1 line
82 KiB
C
Executable File
1 line
82 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
||
All rights reserved.
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
||
the limitations in the disclaimer below) provided that the following conditions are met:
|
||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||
disclaimer in the documentation and/or other materials provided with the distribution.
|
||
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
||
derived from this software without specific prior written permission.
|
||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
||
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||
DAMAGE. */
|
||
|
||
#include "linkmng.h"
|
||
#define FILE_NUM 128
|
||
|
||
/* Copyright (c) 1999 by QUALCOMM Incorporated */
|
||
|
||
/************************************************************************
|
||
* routines to manage multiple link history lists
|
||
************************************************************************/
|
||
|
||
#include "linkmng.h"
|
||
#define FILE_NUM 128
|
||
|
||
#define LINK_HISTORY_VERSION 4 // increment this when changing the HistoryStruct
|
||
|
||
#define LINK_TOC_TYPE 'LToc'
|
||
#define LINK_RESID 130
|
||
|
||
// until ListView is taught about items with names > 31 characters
|
||
typedef Str31 URLNameStr;
|
||
|
||
|
||
//
|
||
// Data structures for link management
|
||
//
|
||
|
||
/* LinkTypeEnum - types of links stored in the link history */
|
||
typedef enum {
|
||
ltAd=0,
|
||
ltHttp,
|
||
ltFtp,
|
||
ltMail,
|
||
ltDirectoryServices,
|
||
ltSetting,
|
||
ltFile,
|
||
LinkTypeLimit
|
||
} LinkTypeEnum;
|
||
|
||
/* LinkLabelEnum - labels for links */
|
||
typedef enum {
|
||
llRemindMe=1,
|
||
llBookmarked,
|
||
llAttempted,
|
||
llNone,
|
||
llNotDisplayed,
|
||
LinkLabelLimit
|
||
} LinkLabelEnum;
|
||
|
||
/* Structure for a given history entry, version 1 */
|
||
typedef struct
|
||
{
|
||
short version; /* the version of the link history entry */
|
||
long hashName; /* hash value on history name */
|
||
URLNameStr name; /* the name of the url */
|
||
unsigned long cacheSeconds; /* date this url was last clicked */
|
||
LinkTypeEnum type; /* the type of url this is */
|
||
long dirty:1; /* has the history entry been modified */
|
||
long deleted:1; /* has the history entry been deleted */
|
||
long thumb:1; /* does this history entry have a thumbnail */
|
||
long label:4; /* label */
|
||
long incompleteAd:1; /* this entry is an ad that's not ready yo be displayed in the history */
|
||
long remind:1; /* remind the user to visit this link */
|
||
long spare:23; /* leftovers */
|
||
long urlOffset; /* offset in file where the real url is at */
|
||
long imageOffset; /* offset in file where the location of the icon is at */
|
||
Handle hUrl; /* Handle to URL this entry will link to */
|
||
AdId adId; /* ID of Ad this entry refers to */
|
||
} HistoryStruct, *HistoryStructPtr, **HistoryStructHandle;
|
||
|
||
/* Structure to keep track of an infividual history file */
|
||
typedef struct HistoryDStruct
|
||
{
|
||
FSSpec spec; /* the history file */
|
||
HistoryStructHandle theData; /* the toc */
|
||
Boolean ro; /* read only */
|
||
Boolean dirty; /* is the history file dirty? */
|
||
} HistoryDesc, *HistoryDPtr, **HistoryDHandle;
|
||
|
||
/* Structure just enough info for the history window. Will sort this puppy. */
|
||
typedef struct
|
||
{
|
||
URLNameStr name; /* the name of the url */
|
||
unsigned long cacheSeconds; /* date this url was last clicked */
|
||
LinkTypeEnum type; /* the type of url this is */
|
||
VLNodeID nodeId; /* id of this node, calculated from which and index */
|
||
LinkLabelEnum label; /* the label of this history entry */
|
||
} ShortHistoryStruct, *ShortHistoryStructPtr, **ShortHistoryStructHandle;
|
||
|
||
/* Structure to maintain a cache of preview icon handles */
|
||
typedef struct LHPIconCacheStruct
|
||
{
|
||
Handle theIcon;
|
||
AdId adId;
|
||
ICacheHandle next;
|
||
} LHPIconCacheStruct, *LHPIconCachePtr, **LHPIconCacheHandle;
|
||
|
||
//
|
||
// Globals for link management
|
||
//
|
||
|
||
// Global list of all known history files
|
||
HistoryDHandle gHistories = nil;
|
||
|
||
// Global list of loaded preview icons
|
||
LHPIconCacheHandle gPreviewIcons = nil;
|
||
|
||
// Global spec pointing inside the Link History FOlder
|
||
FSSpec gLinkHistoryFolder;
|
||
|
||
// GLobal list of all preview icons
|
||
|
||
// Types of links we care about if following them fails with some error
|
||
LinkTypeEnum gLabelTheseLinks[] = {ltAd, ltHttp, ltFtp, ltMail, ltDirectoryServices};
|
||
|
||
//
|
||
// Some helpful #defines
|
||
//
|
||
|
||
#ifdef DEBUG
|
||
#define COMPACT_THRESHHOLD 2
|
||
#else
|
||
#define COMPACT_THRESHHOLD 10
|
||
#endif
|
||
|
||
#define AGE_INTERVAL 60*60*60 // age once an hour
|
||
|
||
#define MAIN_HISTORY_FILE 0
|
||
#define DEFAULT_LINK_TYPE_ICON HTTP_LINK_TYPE_ICON
|
||
#define This (*gHistories)[which]
|
||
#define NHistoryFiles (gHistories ? GetHandleSize_(gHistories)/sizeof(HistoryDesc) : 0)
|
||
|
||
//
|
||
// Link management prototypes
|
||
//
|
||
|
||
OSErr WriteHistTOC(short which);
|
||
Handle GetHistoryData(short which,short index,Boolean readFromDisk);
|
||
OSErr ReadHistTOC(short which);
|
||
static void ZapHistoryFile(short which, Boolean destroy);
|
||
OSErr RegenerateLinkHistory(short which, Boolean rebuild);
|
||
long HistMatchFound(long hashName,Handle theUrl,short which);
|
||
void ReadHistFileList(FSSpec *pSpec,Boolean reread);
|
||
OSErr AddHistoryToTOC(short which,UPtr name,long hashName,LinkTypeEnum type, LinkLabelEnum label,Boolean thumb,Handle url,AdId adId);
|
||
OSErr SaveIndHistoryFile(short which);
|
||
void DeleteHistEntryFromTOC(short which, short index);
|
||
Boolean TimeToCompactTOC(short which);
|
||
OSErr CompactHistTOC(short which);
|
||
short LinkTypeToIconID(LinkTypeEnum type);
|
||
VLNodeID EntryToNodeId(short which, short index);
|
||
void NodeIdToEntry(VLNodeID id, short *which, short *index);
|
||
OSErr AddURLToHistory(short which, PStr url, PStr name, OSErr urlOpenErr);
|
||
short MenuItemNameToIconID(short menuID, short item);
|
||
LinkTypeEnum LinkType(ProtocolEnum protocol, PStr proto);
|
||
void LinkUpdateCacheDate(short which, short index);
|
||
short HistoryCount(short which);
|
||
Boolean LocateAdInHistories(AdId adId, short *which, short *index);
|
||
short AgeHistoryFile(short which);
|
||
void LabelToString(LinkLabelEnum label, Str255 dateStr);
|
||
Boolean LabelableLink(LinkTypeEnum linkType);
|
||
Boolean CorrectVersion(HistoryStructHandle theToc);
|
||
void UpdateLinkLabel(HistoryStructPtr entry, OSErr err);
|
||
LinkLabelEnum OpenErrToLabel(OSErr err);
|
||
Boolean InterestingProtocol(Str255 proto);
|
||
void MakeLinkName(Str255 host, Str255 query, URLNameStr urlName);
|
||
|
||
// Sorting
|
||
OSErr BuildListOfHistoriesForWindow(ShortHistoryStructHandle *histories, Boolean needsSort, LinkSortTypeEnum sortType);
|
||
void SortShortHistoryHandle(ShortHistoryStructHandle toSort, int (*compare)());
|
||
int HistTypeCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
|
||
int HistNameCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
|
||
int HistDateCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
|
||
int HistRemindCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
|
||
void SwapHist(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
|
||
|
||
// Ad preview stuff
|
||
OSErr CreateIconFromAdGraphic(AdId adId, FSSpecPtr adGraphic);
|
||
void AdIdToName(AdId adId, URLNameStr name);
|
||
Boolean NameToAdId(URLNameStr name, AdId *ad);
|
||
OSErr DeleteAdGraphic(AdId adId);
|
||
OSErr IconFromAd(FSSpecPtr iconSpec, FSSpecPtr adSpec);
|
||
OSErr MakeLHIconFile(GWorldPtr gWorld, Rect *pRect, FSSpecPtr iconSpec, GraphicsImportComponent importer, FSSpecPtr sourceSpec);
|
||
void PurgeLinkHistoryPreviewOrphans(void);
|
||
|
||
// Icon cache management
|
||
void AddIconToPVICache(Handle theIcon, AdId adId);
|
||
LHPIconCacheHandle FindPVICache(AdId adId);
|
||
void RemoveIconFromPVICache(AdId adId);
|
||
void RemovePVIFromPVICache(LHPIconCacheHandle *toRemove);
|
||
|
||
// Icon building routines, mostly taken from MakeIcon
|
||
OSErr SetUpPixMap( short depth, Rect *bounds,CTabHandle colors,PixMapHandle aPixMap);
|
||
void TearDownPixMap(PixMapHandle pix);
|
||
Handle MakeIconMask(GWorldPtr srcGWorld, Rect *srcRect, short iconSize);
|
||
Handle MakeIconLo(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize, RGBColor *transColor);
|
||
void FreeBitMap(BitMap *Bits);
|
||
void CalcOffScreen(register Rect *frame,register long *needed, register short *rows);
|
||
void NewBitMap(Rect *frame,BitMap *theMap);
|
||
void NewMaskedBitMap(BitMap *srcBits, BitMap *maskBits, Rect *srcRect);
|
||
|
||
// Nickname routines we've assimilated
|
||
extern OSErr KillNickTOC(FSSpecPtr spec);
|
||
extern Boolean NeatenLine(UPtr line, long *len);
|
||
|
||
/************************************************************************
|
||
* AddURLToMainHistory - add a URL to the main history file
|
||
************************************************************************/
|
||
OSErr AddURLToMainHistory(PStr url, PStr name, OSErr urlOpenErr)
|
||
{
|
||
return (AddURLToHistory(MAIN_HISTORY_FILE, url, name, urlOpenErr));
|
||
}
|
||
|
||
/************************************************************************
|
||
* AddURLToMainHistory - add a URL to the main history file
|
||
************************************************************************/
|
||
OSErr AddURLToHistory(short which, PStr url, PStr name, OSErr urlOpenErr)
|
||
{
|
||
OSErr err = noErr;
|
||
ProtocolEnum protocol;
|
||
Str255 proto,host,query;
|
||
long hashName;
|
||
Handle hUrl = nil;
|
||
URLNameStr urlName;
|
||
LinkLabelEnum linkLabel = llNone;
|
||
LinkTypeEnum linkType;
|
||
short index;
|
||
|
||
// don't add the URL if it wasn't parsable
|
||
if (urlOpenErr == 1) return (urlOpenErr);
|
||
|
||
// parse the URL. See what it is.
|
||
if (!(err=ParseURL(url,proto,host,query)))
|
||
{
|
||
// see if this is one of the protocols we should be keeping history entries for
|
||
if (!InterestingProtocol(proto)) return (userCanceledErr);
|
||
|
||
protocol = FindSTRNIndex(ProtocolStrn,proto);
|
||
FixURLString(host);
|
||
if (protocol!=proMail) FixURLString(query);
|
||
|
||
//
|
||
// Figure out the history entry data
|
||
//
|
||
|
||
// make sure the name of the url contains something interesting
|
||
if (name && name[0])
|
||
{
|
||
// use the name passed in
|
||
PStrCopy(urlName, name, sizeof(urlName));
|
||
}
|
||
else
|
||
{
|
||
if (host[0])
|
||
{
|
||
// make a name for this link entry out of the host and query portions of the URL itself
|
||
MakeLinkName(host, query, urlName);
|
||
}
|
||
else
|
||
{
|
||
// URL had no host. Use the actual URL
|
||
PStrCopy(urlName, url, sizeof(urlName));
|
||
}
|
||
}
|
||
|
||
// The name of the history entry will be a hash of the url itself
|
||
hashName = NickHashString(url);
|
||
|
||
// What sort of link is this?
|
||
linkType = LinkType(protocol, proto);
|
||
|
||
// Was this link successfully launched?
|
||
if (urlOpenErr != noErr)
|
||
{
|
||
// is this a link type we should label?
|
||
if (LabelableLink(linkType))
|
||
{
|
||
// the link was attempted, but probably failed.
|
||
linkLabel = OpenErrToLabel(urlOpenErr);
|
||
}
|
||
}
|
||
|
||
// Turn the url into a handle
|
||
hUrl = NuHTempOK(0);
|
||
if (!hUrl) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
|
||
err = PtrPlusHand(url+1,hUrl,url[0]);
|
||
if (err) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));
|
||
|
||
//
|
||
// Add the new history entry
|
||
//
|
||
|
||
// Make sure the history files are around somewhere
|
||
err = GenHistoriesList();
|
||
|
||
if (err == noErr)
|
||
{
|
||
// Is this entry already in the history file?
|
||
if ((index=HistMatchFound(hashName, hUrl, which)) >= 0)
|
||
{
|
||
// adjust the label of the link ...
|
||
UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), urlOpenErr);
|
||
|
||
// update the date in the TOC ...
|
||
LinkUpdateCacheDate(which, index);
|
||
}
|
||
else
|
||
{
|
||
// If not, add the history to the history file
|
||
AdId junk;
|
||
|
||
junk.server = junk.ad = 0;
|
||
err = AddHistoryToTOC(which, urlName, hashName, linkType, linkLabel, false, hUrl, junk);
|
||
if (err == noErr)
|
||
{
|
||
// save the history file
|
||
err = SaveIndHistoryFile(which);
|
||
}
|
||
}
|
||
|
||
// Update the Link History window ...
|
||
LinkTickle();
|
||
}
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* MakeLinkName - build a name for a link history entry
|
||
************************************************************************/
|
||
void MakeLinkName(Str255 host, Str255 query, URLNameStr urlName)
|
||
{
|
||
short linkNameLength = sizeof(URLNameStr);
|
||
|
||
// initialize the name
|
||
WriteZero(urlName, linkNameLength);
|
||
|
||
// start with the host name
|
||
PCopy(urlName,host);
|
||
|
||
// more room?
|
||
if (urlName[0] < linkNameLength-1)
|
||
{
|
||
// add a slash
|
||
urlName[urlName[0]+1] = '/';
|
||
urlName[0] += 1;
|
||
|
||
// tack on the query
|
||
if (query[0]) PSCat_C(urlName,query,linkNameLength);
|
||
}
|
||
|
||
// replace last character with elipsis, if we reached the maximum length
|
||
if (urlName[0] == linkNameLength-1)
|
||
{
|
||
urlName[urlName[0]] = '<EFBFBD>';
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* InterestingProtocol - see if this is a protocol worth remembering
|
||
************************************************************************/
|
||
Boolean InterestingProtocol(Str255 proto)
|
||
{
|
||
Boolean remember = false;
|
||
Str255 insterestingProtocols, token;
|
||
UPtr spot;
|
||
|
||
if (proto[0])
|
||
{
|
||
// read in the list of interesting protocols
|
||
GetRString(insterestingProtocols,LINK_INTERESTING_PROTO);
|
||
|
||
// see if proto is one of them
|
||
for(spot=insterestingProtocols+1;PToken(insterestingProtocols,token,&spot,",") && !remember;)
|
||
{
|
||
if (StringSame(token, proto)) remember = true;
|
||
}
|
||
}
|
||
|
||
return (remember);
|
||
}
|
||
|
||
/************************************************************************
|
||
* UpdateLinkLabel - update the label of a link according to an error
|
||
************************************************************************/
|
||
void UpdateLinkLabel(HistoryStructPtr entry, OSErr err)
|
||
{
|
||
if (LabelableLink(entry->type) && (err!=noErr))
|
||
{
|
||
// adjust the label unless the user cancelled ...
|
||
if (err != olaCancel) entry->label = OpenErrToLabel(err);
|
||
|
||
// remind the user later if we should ...
|
||
entry->remind = (err == oldaRemind);
|
||
gNeedRemind |= (entry->remind!=0);
|
||
}
|
||
else entry->label = llNone;
|
||
}
|
||
|
||
/************************************************************************
|
||
* OpenErrToLabel - given an error code, return the label we should use
|
||
************************************************************************/
|
||
LinkLabelEnum OpenErrToLabel(OSErr err)
|
||
{
|
||
LinkLabelEnum result = llNone;
|
||
|
||
if ((err!=noErr))
|
||
{
|
||
if (err == oldaBookmark) result = llBookmarked;
|
||
else if (err == oldaRemind) result = llRemindMe;
|
||
else result = llAttempted;
|
||
}
|
||
|
||
return (result);
|
||
}
|
||
|
||
/************************************************************************
|
||
* RegenerateLinkHistory - make sure the history list is in memory
|
||
************************************************************************/
|
||
static OSErr RegenerateLinkHistory(short which, Boolean rebuild)
|
||
{
|
||
OSErr err = noErr;
|
||
UHandle hand;
|
||
|
||
if (rebuild) ZapHistoryFile(which, true);
|
||
|
||
if (!(*gHistories)[which].theData) // If handle for data doesn't exist, create it
|
||
{
|
||
hand = NuHTempOK(0L);
|
||
if (!hand) err = WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError());
|
||
(*gHistories)[which].theData = (HistoryStructHandle)hand;
|
||
}
|
||
else // Handle exists
|
||
{
|
||
if (!rebuild)
|
||
return (noErr);
|
||
}
|
||
|
||
if (err != noErr || rebuild)
|
||
{
|
||
err = ReadHistTOC(which);
|
||
}
|
||
|
||
if (err)
|
||
ZapHandle((*gHistories)[which].theData);
|
||
|
||
return(err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ZapHistoryFile - release memory for all histories in a file
|
||
************************************************************************/
|
||
static void ZapHistoryFile(short which, Boolean destroy)
|
||
{
|
||
short i;
|
||
HistoryStructHandle history = gHistories ? (*gHistories)[which].theData : nil;
|
||
|
||
if (history)
|
||
{
|
||
// throw away all the URL and image handles we have laying around
|
||
for (i=0;i<HistoryCount(which);i++)
|
||
{
|
||
if ((*history)[i].hUrl)
|
||
{
|
||
ZapHandle((*history)[i].hUrl);
|
||
(*history)[i].hUrl = nil;
|
||
}
|
||
}
|
||
|
||
// if we're destroying, trash the actual history list as well
|
||
if (destroy)
|
||
{
|
||
ZapHandle((*gHistories)[which].theData);
|
||
(*gHistories)[which].theData = nil;
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* ZapHistoriesList - destroy the history file list, or make it smaller
|
||
************************************************************************/
|
||
void ZapHistoriesList(Boolean destroy)
|
||
{
|
||
short which;
|
||
short n = gHistories ? NHistoryFiles : 0;
|
||
|
||
// clean up the inidividual history files
|
||
for (which = 0; which < n; which++)
|
||
{
|
||
ZapHistoryFile(which, destroy);
|
||
}
|
||
|
||
// if we're destroying, zap the handle to all the histories as well.
|
||
if (destroy)
|
||
{
|
||
if (gHistories) ZapHandle(gHistories);
|
||
gHistories = nil;
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* GenHistoriesList - generate the history file list
|
||
************************************************************************/
|
||
OSErr GenHistoriesList(void)
|
||
{
|
||
OSErr err = noErr;
|
||
Str31 name;
|
||
FSSpec folderSpec;
|
||
HistoryDesc ad;
|
||
|
||
Zero(ad);
|
||
|
||
// do nothing if the history files have already been loaded.
|
||
if (gHistories) return(noErr);
|
||
|
||
/*
|
||
* allocate empty handle for new
|
||
*/
|
||
|
||
if (!(gHistories=NuHandle(0L)))
|
||
{
|
||
WarnUser(MEM_ERR,err = MemError());
|
||
return (err);
|
||
}
|
||
|
||
/*
|
||
* add the Eudora History file
|
||
*/
|
||
|
||
FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(name,LINK_HISTORY_FILE),&ad.spec);
|
||
if (PtrPlusHand_(&ad,gHistories,sizeof(ad))) DieWithError(MEM_ERR,MemError());
|
||
RegenerateLinkHistory(MAIN_HISTORY_FILE, true);
|
||
|
||
/*
|
||
* add any additional history files in the Link History Folder
|
||
*/
|
||
|
||
ReadHistFileList(&folderSpec,false);
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ReadHistFileList - find extra link history files
|
||
************************************************************************/
|
||
void ReadHistFileList(FSSpec *pSpec,Boolean reread)
|
||
{
|
||
Str31 name;
|
||
CInfoPBRec hfi;
|
||
HistoryDesc ad;
|
||
long dirId;
|
||
short count = 1;
|
||
|
||
/*
|
||
* create the folder if we need it ...
|
||
*/
|
||
if (SubFolderSpec(LINK_HISTORY_FOLDER,pSpec)!=noErr)
|
||
{
|
||
DirCreate(Root.vRef,Root.dirId,GetRString(name,LINK_HISTORY_FOLDER),&dirId);
|
||
SimpleMakeFSSpec(pSpec->vRefNum,dirId,"\p",&gLinkHistoryFolder);
|
||
return;
|
||
}
|
||
else
|
||
gLinkHistoryFolder = *pSpec;
|
||
|
||
/*
|
||
* read in the history files ...
|
||
*/
|
||
|
||
Zero(ad);
|
||
hfi.hFileInfo.ioNamePtr = name;
|
||
hfi.hFileInfo.ioFDirIndex = 0;
|
||
while (!DirIterate(pSpec->vRefNum,pSpec->parID,&hfi))
|
||
{
|
||
if (hfi.hFileInfo.ioFlFndrInfo.fdType=='TEXT' && hfi.hFileInfo.ioFlFndrInfo.fdCreator==CREATOR)
|
||
{
|
||
SimpleMakeFSSpec(pSpec->vRefNum,pSpec->parID,name,&ad.spec);
|
||
if (!CanWrite(&ad.spec,&ad.ro)) ad.ro = !ad.ro; /* opposite sense! */
|
||
if (PtrPlusHand_(&ad,gHistories,sizeof(ad))) DieWithError(MEM_ERR,MemError());
|
||
|
||
// read it in
|
||
RegenerateLinkHistory(count++, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* AddHistoryToTOC - add a history entry to the TOC
|
||
************************************************************************/
|
||
OSErr AddHistoryToTOC(short which,UPtr name,long hashName,LinkTypeEnum type,LinkLabelEnum label,Boolean thumb,Handle url,AdId adId)
|
||
{
|
||
long currHistCount;
|
||
HistoryStructHandle histories = (*gHistories)[which].theData;
|
||
OSErr err = noErr;
|
||
HistoryStruct histInfo;
|
||
|
||
//
|
||
// Make sure the history list is around
|
||
//
|
||
|
||
if (RegenerateLinkHistory(which, false)!=noErr) return (err);
|
||
|
||
//
|
||
// Make room for the new history entry
|
||
//
|
||
|
||
HUnlock((Handle)histories);
|
||
currHistCount = HistoryCount(which);
|
||
if (currHistCount > 0)
|
||
{
|
||
SetHandleBig_((Handle)histories,(currHistCount + 1)*sizeof(HistoryStruct));
|
||
if (err = MemError())
|
||
return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));
|
||
|
||
}
|
||
else
|
||
{
|
||
ZapHandle((*gHistories)[which].theData);
|
||
histories = NuHTempOK(sizeof(HistoryStruct));
|
||
if (!histories)
|
||
return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
|
||
else
|
||
(*gHistories)[which].theData = histories;
|
||
}
|
||
|
||
//
|
||
// Add the TOC entry
|
||
//
|
||
|
||
// Fill in the basic data
|
||
WriteZero(&histInfo,sizeof(histInfo));
|
||
histInfo.version = LINK_HISTORY_VERSION;
|
||
histInfo.hashName = hashName;
|
||
PSCopy(histInfo.name, name);
|
||
histInfo.cacheSeconds = LocalDateTime();
|
||
histInfo.type = type;
|
||
histInfo.dirty = true;
|
||
histInfo.deleted = false;
|
||
histInfo.thumb = thumb;
|
||
histInfo.label = label;
|
||
histInfo.incompleteAd = ((type == ltAd) && !(url && *url && **url));
|
||
histInfo.urlOffset = (-1L);
|
||
histInfo.imageOffset = (-1L);
|
||
histInfo.adId = adId;
|
||
|
||
// now the potentially large url
|
||
if (url && *url && **url)
|
||
{
|
||
histInfo.hUrl = url;
|
||
}
|
||
|
||
// Put hist info into the history TOC array
|
||
(*(histories))[currHistCount] = histInfo;
|
||
|
||
UL(histories);
|
||
(*gHistories)[which].dirty = true;
|
||
return(0);
|
||
}
|
||
|
||
/************************************************************************
|
||
* SaveAllHistoryFiles - save all history files that need it
|
||
************************************************************************/
|
||
OSErr SaveAllHistoryFiles(void)
|
||
{
|
||
OSErr err = noErr;
|
||
short hFiles;
|
||
|
||
for (hFiles = 0; hFiles < NHistoryFiles; hFiles++)
|
||
{
|
||
if ((*gHistories)[hFiles].dirty)
|
||
err = err || SaveIndHistoryFile(hFiles);
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* SaveIndHistoryFile - save an individual History file
|
||
************************************************************************/
|
||
OSErr SaveIndHistoryFile(short which)
|
||
{
|
||
Str31 aliasCmd;
|
||
int err;
|
||
long bytes,offset;
|
||
short refN=0;
|
||
long i, count;
|
||
FSSpec spec,tmpSpec;
|
||
Boolean junk;
|
||
Handle tempHandle;
|
||
|
||
#ifdef DEBUG
|
||
if (InAThread()) return noErr;
|
||
#endif
|
||
|
||
/*
|
||
* do we need to save it?
|
||
*/
|
||
if (!(*gHistories)[which].dirty) return(noErr);
|
||
|
||
/*
|
||
* find the file
|
||
*/
|
||
spec = (*gHistories)[which].spec;
|
||
if (err=FSpMyResolve(&spec,&junk))
|
||
{
|
||
FileSystemError(SAVE_LINK_HISTORY,spec.name,err);
|
||
return(err);
|
||
}
|
||
|
||
/*
|
||
* make && open a temp file
|
||
*/
|
||
if (err=NewTempSpec(spec.vRefNum,spec.parID,nil,&tmpSpec))
|
||
goto done;
|
||
if (err=FSpCreate(&tmpSpec,CREATOR,MAILBOX_TYPE,smSystemScript))
|
||
{FileSystemError(SAVE_LINK_HISTORY,tmpSpec.name,err); goto done;}
|
||
if (err=FSpOpenDF(&tmpSpec,fsRdWrPerm,&refN))
|
||
{FileSystemError(OPEN_LINK_HISTORY,tmpSpec.name,err); goto done;}
|
||
|
||
|
||
count = HistoryCount(which); // Get history entry count
|
||
|
||
/*
|
||
* Write out the url
|
||
*/
|
||
|
||
GetRString(aliasCmd,LINK_CMD);
|
||
PCatC(aliasCmd,' ');
|
||
HLock((Handle)(*gHistories)[which].theData);
|
||
for (i=0;!err && i<count;i++)
|
||
{
|
||
CycleBalls();
|
||
if ((*((*gHistories)[which].theData))[i].dirty) // Nickname has been modified...use in memory copy
|
||
tempHandle = GetHistoryData(which,i,false);
|
||
else // Nickname hasn't been modified, use on disk copy if necessary
|
||
tempHandle = GetHistoryData(which,i,true);
|
||
|
||
(*((*gHistories)[which].theData))[i].dirty = false;
|
||
|
||
bytes = (tempHandle && *tempHandle && **tempHandle) ? GetHandleSize_(tempHandle) : 0;
|
||
|
||
if (bytes >0 && !(*((*gHistories)[which].theData))[i].deleted)
|
||
{
|
||
GetFPos(refN,&offset);
|
||
(*((*gHistories)[which].theData))[i].urlOffset = offset;
|
||
}
|
||
else
|
||
{
|
||
// (*gHistories)[which] entry is missing a URL. If it's not an incomplete ad, remove it from the TOC, and skip it.
|
||
if (!(*((*gHistories)[which].theData))[i].incompleteAd) (*((*gHistories)[which].theData))[i].deleted = true;
|
||
continue;
|
||
}
|
||
|
||
if ((!(*((*gHistories)[which].theData))[i].deleted) && bytes > 0 && !(err = FSWriteP(refN,aliasCmd)))
|
||
{
|
||
HLock(tempHandle);
|
||
if (!(err = AWrite(refN,&bytes,*tempHandle)))
|
||
{
|
||
bytes = 1;
|
||
err = AWrite(refN,&bytes,"\015");
|
||
}
|
||
HUnlock(tempHandle);
|
||
}
|
||
}
|
||
|
||
UL((*gHistories)[which].theData);
|
||
|
||
GetFPos(refN,&bytes);
|
||
SetEOF(refN,bytes);
|
||
MyFSClose(refN); refN = 0;
|
||
|
||
/* do the deed */
|
||
if (!err) err = ExchangeAndDel(&tmpSpec,&spec);
|
||
if (!err) (*gHistories)[which].dirty = False;
|
||
|
||
WriteHistTOC(which);
|
||
|
||
done:
|
||
if ((*gHistories)[which].theData) UL((*gHistories)[which].theData);
|
||
if (refN) MyFSClose(refN);
|
||
if (err) FSpDelete(&tmpSpec);
|
||
return(err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* WriteHistTOC - write the toc for a history file
|
||
**********************************************************************/
|
||
OSErr WriteHistTOC(short which)
|
||
{
|
||
uLong fileModDate;
|
||
FSSpec spec = (*gHistories)[which].spec;
|
||
OSErr err = noErr;
|
||
short refN;
|
||
HistoryStructHandle theData;
|
||
short oldResF = CurResFile();
|
||
|
||
#ifdef DEBUG
|
||
if (InAThread()) return noErr;
|
||
#endif
|
||
|
||
if (TimeToCompactTOC(which)) CompactHistTOC(which);
|
||
|
||
theData = DupHandle((*gHistories)[which].theData);
|
||
if ((theData) && ((err=MemError())==noErr))
|
||
{
|
||
IsAlias(&spec,&spec);
|
||
KillNickTOC(&spec);
|
||
FSpCreateResFile(&spec,CREATOR,'TEXT',smSystemScript);
|
||
fileModDate = AFSpGetMod(&spec);
|
||
|
||
if (-1!=(refN=FSpOpenResFile(&spec,fsRdWrPerm)))
|
||
{
|
||
AddResource(theData,LINK_TOC_TYPE,LINK_RESID,"");
|
||
err = ResError();
|
||
if (!err)
|
||
{
|
||
UpdateResFile(refN);
|
||
err = ResError();
|
||
}
|
||
CloseResFile(refN);
|
||
}
|
||
else err = ResError();
|
||
|
||
if (!err) err = AFSpSetMod(&spec,fileModDate);
|
||
|
||
UseResFile (oldResF);
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* GetHistoryData - Get the url or image of a given history
|
||
************************************************************************/
|
||
Handle GetHistoryData(short which,short index,Boolean readFromDisk)
|
||
{
|
||
Handle tempHandle;
|
||
HistoryStructHandle histories = (*gHistories)[which].theData;
|
||
FSSpec spec;
|
||
Boolean finished = false;
|
||
int err;
|
||
Str255 line;
|
||
short type;
|
||
Boolean exLine=False;
|
||
long len;
|
||
Handle dataHandle;
|
||
LineIOD lid;
|
||
long theOffset;
|
||
Str31 theCmd;
|
||
|
||
spec = (*gHistories)[which].spec;
|
||
|
||
if (index < 0 || which < 0 || index >= HistoryCount(which) || (*histories)[index].deleted)
|
||
return (nil);
|
||
|
||
tempHandle = (*histories)[index].hUrl;
|
||
|
||
if (!readFromDisk)
|
||
{
|
||
if ((*histories)[index].dirty)
|
||
return (tempHandle);
|
||
|
||
if (tempHandle !=nil && *tempHandle !=nil)
|
||
return (tempHandle);
|
||
}
|
||
|
||
if (tempHandle != nil)
|
||
ZapHandle(tempHandle);
|
||
|
||
theOffset = (*histories)[index].urlOffset;
|
||
GetRString(theCmd,LINK_CMD);
|
||
|
||
if ( theOffset >= 0)
|
||
{
|
||
if (err=FSpOpenLine(&spec,fsRdPerm,&lid))
|
||
{
|
||
if (err !=fnfErr) FileSystemError(OPEN_LINK_HISTORY,spec.name,err);
|
||
return (nil);
|
||
}
|
||
|
||
dataHandle = NuHTempOK(0L);
|
||
if (!dataHandle)
|
||
{
|
||
WarnUser(LINK_HISTORY_GET_DATA_ERR,MemError());
|
||
return (nil);
|
||
|
||
}
|
||
|
||
/*
|
||
* Offset is from the beginning of the line; add in length of the command and length of name
|
||
* and two spaces
|
||
*/
|
||
theOffset += *theCmd + 1;
|
||
SeekLine(theOffset,&lid);
|
||
type = GetLine(line,sizeof(line),&len,&lid);
|
||
if (type==LINE_START)
|
||
do
|
||
{
|
||
// process current line
|
||
len = strlen(line);
|
||
exLine = NeatenLine(line,&len);
|
||
if (exLine && !issep(*line)) // If line was escaped and the first character isn't a space, add one
|
||
{
|
||
if (err=PtrPlusHand_(" ",dataHandle,1)) break;
|
||
}
|
||
if (err=PtrPlusHand_(line,dataHandle,len)) break;
|
||
|
||
// grab the next line; may or may not be ours
|
||
type = GetLine(line,sizeof(line),&len,&lid);
|
||
if (exLine && type==LINE_START) type = LINE_MIDDLE; // extended line means new line is really part of this line
|
||
}
|
||
while (type==LINE_MIDDLE);
|
||
|
||
CloseLine(&lid);
|
||
if (err)
|
||
{
|
||
WarnUser(LINK_HISTORY_GET_DATA_ERR,err);
|
||
ZapHandle(dataHandle);
|
||
return (nil);
|
||
}
|
||
|
||
(*histories)[index].hUrl = dataHandle;
|
||
|
||
UL(dataHandle);
|
||
return (dataHandle);
|
||
}
|
||
else
|
||
return (nil);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ReadHistTOC - read in the history list TOC
|
||
************************************************************************/
|
||
OSErr ReadHistTOC(short which)
|
||
{
|
||
OSErr err = noErr;
|
||
FSSpec lSpec = (*gHistories)[which].spec;
|
||
Boolean sane;
|
||
short refN=0;
|
||
short oldResF = CurResFile();
|
||
HistoryStructHandle theToc = nil;
|
||
UHandle hand = nil;
|
||
|
||
//
|
||
// Open the History file
|
||
//
|
||
|
||
IsAlias(&(*gHistories)[which].spec,&lSpec);
|
||
if (err=FSpRFSane(&lSpec,&sane)) return(-1);
|
||
if (!sane)
|
||
{
|
||
FSpKillRFork(&lSpec);
|
||
return (-1);
|
||
}
|
||
else if (-1!=(refN=FSpOpenResFile(&lSpec,fsRdPerm)))
|
||
{
|
||
// Clear out the old handle
|
||
if ((*gHistories)[which].theData) ZapHandle((*gHistories)[which].theData);
|
||
|
||
//
|
||
// Read to TOC handle out of the file
|
||
//
|
||
|
||
theToc = Get1Resource(LINK_TOC_TYPE,LINK_RESID);
|
||
if (noErr==(err=ResError()))
|
||
{
|
||
short i, count;
|
||
|
||
if (theToc != nil)
|
||
{
|
||
// is this toc the right version?
|
||
if (CorrectVersion(theToc))
|
||
{
|
||
DetachResource(theToc);
|
||
(*gHistories)[which].theData = theToc;
|
||
|
||
// iterate through the toc and reset garbage fields
|
||
count = GetHandleSize_(theToc)/sizeof(HistoryStruct);
|
||
for (i = 0; i < count; i++) (*theToc)[i].hUrl = nil;
|
||
}
|
||
else
|
||
{
|
||
// wrong version of the toc. Lose the history.
|
||
theToc = nil;
|
||
}
|
||
}
|
||
}
|
||
|
||
// got nothing from the resource file
|
||
if (theToc == nil)
|
||
{
|
||
// create a new, empty handle. No history entries are defined.
|
||
hand = NuHTempOK(0L);
|
||
err = MemError();
|
||
if (hand && (err==noErr)) (*gHistories)[which].theData = (HistoryStructHandle)hand;
|
||
}
|
||
|
||
// was there an error?
|
||
if (err != noErr)
|
||
{
|
||
WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err);
|
||
ZapHandle((*gHistories)[which].theData);
|
||
(*gHistories)[which].theData = nil;
|
||
}
|
||
}
|
||
|
||
if (refN) CloseResFile(refN);
|
||
UseResFile (oldResF);
|
||
|
||
return(err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* CorrectVersion - is this HistoryStructHandle something we can handle?
|
||
************************************************************************/
|
||
Boolean CorrectVersion(HistoryStructHandle theToc)
|
||
{
|
||
Boolean result = false;
|
||
|
||
if (theToc)
|
||
{
|
||
if (GetHandleSize(theToc) > 0)
|
||
{
|
||
if ((*theToc)->version == LINK_HISTORY_VERSION)
|
||
result = true;
|
||
}
|
||
}
|
||
|
||
return (result);
|
||
}
|
||
|
||
/************************************************************************
|
||
* HistMatchFound - find a given history in a history file
|
||
************************************************************************/
|
||
long HistMatchFound(long hashName,Handle theUrl,short which)
|
||
{
|
||
long i;
|
||
Handle hUrl = nil;
|
||
URLNameStr tempStr, tempName;
|
||
HistoryStructHandle theHistories = (*gHistories)[which].theData;
|
||
long stop = (GetHandleSize_(theHistories)/sizeof(HistoryStruct));
|
||
Boolean needStringMatch = false;
|
||
long matched = -1;
|
||
HistoryStruct *theStruct, *endStruct;
|
||
|
||
endStruct = (*theHistories)+stop;
|
||
for (theStruct=*theHistories; theStruct<endStruct; theStruct++)
|
||
if (theStruct->hashName == hashName && !theStruct->deleted)
|
||
if (matched==-1) matched = theStruct-*theHistories;
|
||
else {needStringMatch = True; break;}
|
||
|
||
if (!needStringMatch) return(matched);
|
||
|
||
// two names with equal hash values found, compare names.
|
||
PSCopy(tempName,*theUrl);
|
||
*tempName = RemoveChar(' ',tempName+1,*tempName);
|
||
for (i=0;i<stop;i++)
|
||
{
|
||
if ((*theHistories)[i].hashName == hashName && !(*theHistories)[i].deleted)
|
||
{
|
||
hUrl = GetHistoryData(which,i,true); // don't zap the returned handle. It's a part of the history toc.
|
||
if (hUrl)
|
||
{
|
||
PCopy(tempStr, *hUrl);
|
||
*tempStr = RemoveChar(' ',tempStr+1,*tempStr);
|
||
if (StringSame(tempName,tempStr))
|
||
return (i);
|
||
}
|
||
}
|
||
}
|
||
|
||
return (-1);
|
||
}
|
||
|
||
/************************************************************************
|
||
* DeleteHistEntryFromTOC - delete a history entry from the TOC.
|
||
************************************************************************/
|
||
void DeleteHistEntryFromTOC(short which, short index)
|
||
{
|
||
(*(*gHistories)[which].theData)[index].deleted = true;
|
||
(*gHistories)[which].dirty = true;
|
||
|
||
if ((*(*gHistories)[which].theData)[index].type == ltAd) // did we just delete an ad?
|
||
if ((*(*gHistories)[which].theData)[index].thumb!=0) // does it think it has a thumbnail?
|
||
DeleteAdGraphic((*(*gHistories)[which].theData)[index].adId);
|
||
}
|
||
|
||
/************************************************************************
|
||
* TimeToCompactTOC - is it worth our time to compact this TOC?
|
||
************************************************************************/
|
||
Boolean TimeToCompactTOC(short which)
|
||
{
|
||
short deletedCount = 0;
|
||
short historyCount = HistoryCount(which);
|
||
HistoryStructHandle histories = (*gHistories)[which].theData;
|
||
|
||
while (historyCount > 0)
|
||
{
|
||
if ((*histories)[historyCount-1].deleted) deletedCount++;
|
||
historyCount--;
|
||
}
|
||
|
||
return (deletedCount >= COMPACT_THRESHHOLD);
|
||
}
|
||
|
||
/************************************************************************
|
||
* CompactHistTOC - remove deleted entries from the TOC.
|
||
************************************************************************/
|
||
OSErr CompactHistTOC(short which)
|
||
{
|
||
OSErr err = noErr;
|
||
Accumulator histAccu;
|
||
short count = 0, historyCount = HistoryCount(which);
|
||
HistoryStructHandle histories = (*gHistories)[which].theData;
|
||
|
||
if ((err = AccuInit(&histAccu))==noErr)
|
||
{
|
||
while ((count != historyCount) && (err==noErr))
|
||
{
|
||
if ((*histories)[count].deleted)
|
||
; // skip this TOC entry. It's deleted.
|
||
else
|
||
{
|
||
// add this history entry to the Accumulator
|
||
err = AccuAddPtr(&histAccu, &(*histories)[count], sizeof(HistoryStruct));
|
||
}
|
||
count++;
|
||
}
|
||
|
||
if (err == noErr)
|
||
{
|
||
AccuTrim(&histAccu);
|
||
ZapHandle((*gHistories)[which].theData);
|
||
(*gHistories)[which].theData = histAccu.data;
|
||
histAccu.data = nil;
|
||
}
|
||
|
||
AccuZap(histAccu);
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* HistoryCount - return # of history entries
|
||
************************************************************************/
|
||
short HistoryCount(short which)
|
||
{
|
||
if ((*gHistories)[which].theData)
|
||
return ((GetHandleSize_((*gHistories)[which].theData)/sizeof(HistoryStruct)));
|
||
else
|
||
return (0);
|
||
}
|
||
|
||
/************************************************************************
|
||
* AddAllHistoryItems - add all the history items to the list
|
||
************************************************************************/
|
||
void AddAllHistoryItems(ViewListPtr pView, Boolean needsSort, LinkSortTypeEnum sortType)
|
||
{
|
||
OSErr err = noErr;
|
||
short i,count,cur = 0;
|
||
VLNodeInfo info;
|
||
ShortHistoryStructHandle histories = NULL;
|
||
Boolean reverseSort = (sortType&kLinkReverseSort)!=0;
|
||
|
||
// get a sorted list of all history entries to add
|
||
err = BuildListOfHistoriesForWindow(&histories, needsSort, sortType);
|
||
if ((err==noErr) && histories)
|
||
{
|
||
count = GetHandleSize(histories)/sizeof(ShortHistoryStruct);
|
||
for (i=0;i<count;i++)
|
||
{
|
||
cur = reverseSort ? (count - 1 - i) : i;
|
||
|
||
Zero(info);
|
||
PStrCopy(info.name,(*histories)[cur].name,sizeof(info.name));
|
||
info.nodeID = (*histories)[cur].nodeId;
|
||
info.iconID = LinkTypeToIconID((*histories)[cur].type);
|
||
LVAdd(pView, &info);
|
||
}
|
||
|
||
// cleanup, don't need this anymore.
|
||
ZapHandle(histories);
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* ShortHistoryStructHandle - return a sorted list of structures that
|
||
* contain just enough information to add to the history window.
|
||
************************************************************************/
|
||
OSErr BuildListOfHistoriesForWindow(ShortHistoryStructHandle *histories, Boolean needsSort, LinkSortTypeEnum sortType)
|
||
{
|
||
OSErr err = noErr;
|
||
short which, i;
|
||
short numEntries, count;
|
||
int (*compare)() = nil;
|
||
|
||
*histories = nil;
|
||
|
||
// How many history entries are there in all files?
|
||
numEntries = 0;
|
||
for (which = 0; which < NHistoryFiles; which++)
|
||
numEntries += HistoryCount(which);
|
||
|
||
// Make a new handle that's big enough for them all
|
||
*histories = NuHandle(numEntries*sizeof(ShortHistoryStruct));
|
||
err = MemError();
|
||
if (*histories && (err == noErr))
|
||
{
|
||
// fill the new handle with all of the entries
|
||
count = 0;
|
||
for (which = 0; which < NHistoryFiles; which++)
|
||
{
|
||
for (i = 0; i < HistoryCount(which); i++)
|
||
{
|
||
// only add non-deleted items
|
||
if (!((*(*gHistories)[which].theData)[i].deleted) && !((*(*gHistories)[which].theData)[i].incompleteAd))
|
||
{
|
||
PCopy((**histories)[count].name,(*(*gHistories)[which].theData)[i].name);
|
||
(**histories)[count].cacheSeconds = (*(*gHistories)[which].theData)[i].cacheSeconds;
|
||
(**histories)[count].type = (*(*gHistories)[which].theData)[i].type;
|
||
(**histories)[count].nodeId = EntryToNodeId(which, i);
|
||
(**histories)[count].label = (*(*gHistories)[which].theData)[i].label;
|
||
count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Resize the handle down if we hafta.
|
||
if (count != numEntries) SetHandleSize(*histories, count*sizeof(ShortHistoryStruct));
|
||
|
||
// Sort the handle if we need to
|
||
if (needsSort)
|
||
{
|
||
switch (sortType&kLinkSortTypeMask)
|
||
{
|
||
case sType:
|
||
compare = HistTypeCompare;
|
||
break;
|
||
|
||
case sName:
|
||
compare = HistNameCompare;
|
||
break;
|
||
|
||
case sDate:
|
||
compare = HistDateCompare;
|
||
break;
|
||
|
||
case sSpecialRemind:
|
||
compare = HistRemindCompare;
|
||
break;
|
||
}
|
||
|
||
if (compare) SortShortHistoryHandle(*histories, compare);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
WarnUser(MEM_ERR,err);
|
||
*histories = nil;
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* LinkType - Given a protocol, return the type of link
|
||
************************************************************************/
|
||
LinkTypeEnum LinkType(ProtocolEnum protocol, PStr proto)
|
||
{
|
||
LinkTypeEnum type = ltHttp; // assume web link
|
||
Str255 scratch;
|
||
|
||
switch (protocol)
|
||
{
|
||
case proFinger:
|
||
case proPh:
|
||
case proPh2:
|
||
case proLDAP:
|
||
type = ltDirectoryServices;
|
||
break;
|
||
|
||
case proMail:
|
||
type = ltMail;
|
||
break;
|
||
|
||
case proFile:
|
||
case proCompFile:
|
||
type = ltFile;
|
||
break;
|
||
|
||
case proSetting:
|
||
type = ltSetting;
|
||
break;
|
||
|
||
default:
|
||
{
|
||
// make sure this isn't an ftp link
|
||
if (StringSame(proto, GetRString(scratch,ANARCHIE_FTP)))
|
||
type = ltFtp;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return (type);
|
||
}
|
||
|
||
/************************************************************************
|
||
* LinkTypeToIconID - Given a protocol, return the icon to be used
|
||
************************************************************************/
|
||
short LinkTypeToIconID(LinkTypeEnum type)
|
||
{
|
||
short iconID;
|
||
|
||
if (type==ltAd)
|
||
{
|
||
// Use the thumbnail if present, a web link otherwise.
|
||
iconID = kCustomIconResource;
|
||
}
|
||
else
|
||
{
|
||
// assign an icon based on the type of URL this is
|
||
switch (type)
|
||
{
|
||
case ltDirectoryServices:
|
||
iconID = MenuItemNameToIconID(WINDOW_MENU,WIN_PH_ITEM);
|
||
break;
|
||
|
||
case ltMail:
|
||
iconID = MAILTO_LINK_TYPE_ICON;
|
||
break;
|
||
|
||
case ltHttp:
|
||
iconID = HTTP_LINK_TYPE_ICON;
|
||
break;
|
||
|
||
case ltFtp:
|
||
iconID = FTP_LINK_TYPE_ICON;
|
||
break;
|
||
|
||
case ltFile:
|
||
iconID = MenuItemNameToIconID(FILE_MENU,FILE_SAVE_AS_ITEM);
|
||
break;
|
||
|
||
case ltSetting:
|
||
iconID = SETTINGS_ICON;
|
||
break;
|
||
|
||
default:
|
||
iconID = DEFAULT_LINK_TYPE_ICON; // assume it's a web link
|
||
break;
|
||
}
|
||
}
|
||
|
||
return (iconID);
|
||
}
|
||
|
||
/************************************************************************
|
||
* MenuItemNameToIconID - return the icon id of a menu item by name
|
||
************************************************************************/
|
||
short MenuItemNameToIconID(short menuID, short item)
|
||
{
|
||
MenuHandle mh;
|
||
short id = DEFAULT_LINK_TYPE_ICON;
|
||
Str255 itemText;
|
||
|
||
if (mh=GetMHandle(menuID))
|
||
{
|
||
GetMenuItemText(mh, item, itemText);
|
||
id = Names2Icon(itemText,"\p");
|
||
}
|
||
|
||
return (id);
|
||
}
|
||
|
||
/************************************************************************
|
||
* EntryToNodeId - create an ID out of file # and index
|
||
************************************************************************/
|
||
VLNodeID EntryToNodeId(short which, short index)
|
||
{
|
||
VLNodeID id;
|
||
|
||
id = (which*0x10000) + index;
|
||
|
||
return (id);
|
||
}
|
||
|
||
/************************************************************************
|
||
* NodeIdToEntry - return the file # and index of a given id
|
||
************************************************************************/
|
||
void NodeIdToEntry(VLNodeID id, short *which, short *index)
|
||
{
|
||
*which = HiWord(id);
|
||
*index = LoWord(id);
|
||
}
|
||
|
||
/************************************************************************
|
||
* DeleteHistoryEntry - delete a history entry from the History window
|
||
************************************************************************/
|
||
void DeleteHistoryEntry(VLNodeInfo *info)
|
||
{
|
||
short which;
|
||
short index;
|
||
|
||
NodeIdToEntry(info->nodeID, &which, &index);
|
||
DeleteHistEntryFromTOC(which, index);
|
||
}
|
||
|
||
/************************************************************************
|
||
* OpenHistoryEntry - open a history entry from the History window
|
||
************************************************************************/
|
||
OSErr OpenHistoryEntry(VLNodeInfo *info)
|
||
{
|
||
short which;
|
||
short index;
|
||
Handle hUrl;
|
||
Str255 proto;
|
||
short len;
|
||
OSErr openErr = noErr;
|
||
|
||
// Figure out which file this entry is in ...
|
||
NodeIdToEntry(info->nodeID, &which, &index);
|
||
|
||
// read in the URL from the file
|
||
// Note: GetHistoryData returns a handle that belongs to the history toc struct. Don't trash it!
|
||
if ((*(*gHistories)[which].theData)[index].dirty) hUrl = GetHistoryData(which,index,false);
|
||
else hUrl = GetHistoryData(which,index,true);
|
||
|
||
if (hUrl && (len = GetHandleSize(hUrl)))
|
||
{
|
||
char *url = LDRef(hUrl);
|
||
|
||
// parse and open this URL
|
||
if (fnfErr==(openErr=OpenLocalURLPtr(url,len,nil,nil,false)))
|
||
if ((openErr=ParseProtocolFromURLPtr(url,len,proto))==noErr)
|
||
openErr = OpenOtherURLPtr(proto,url,len);
|
||
UL(hUrl);
|
||
|
||
// update the link's label
|
||
UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), openErr);
|
||
|
||
// Update the date in the History TOC
|
||
LinkUpdateCacheDate(which, index);
|
||
|
||
// Update the Link history window
|
||
LinkTickle();
|
||
}
|
||
|
||
return (openErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* GetDateString - given the ID of a node, return the date in a string
|
||
************************************************************************/
|
||
Boolean GetDateString(VLNodeID id, Str255 dateStr)
|
||
{
|
||
short which;
|
||
short index;
|
||
Str31 zone;
|
||
|
||
// Find the history entry
|
||
NodeIdToEntry(id, &which, &index);
|
||
|
||
// Does this entry have a label?
|
||
if ((*(*gHistories)[which].theData)[index].label!=llNone)
|
||
{
|
||
LabelToString((*(*gHistories)[which].theData)[index].label,dateStr);
|
||
}
|
||
else
|
||
{
|
||
// return the standard date string
|
||
*zone = 0;
|
||
SecsToLocalDateTime((*(*gHistories)[which].theData)[index].cacheSeconds-ZoneSecs(), ZoneSecs(), zone, dateStr);
|
||
}
|
||
|
||
return (true);
|
||
}
|
||
|
||
/************************************************************************
|
||
* IsMarkedRemind - is this cell one we need to remind the user about?
|
||
************************************************************************/
|
||
Boolean IsMarkedRemind(VLNodeID id)
|
||
{
|
||
Boolean result = false;
|
||
short which;
|
||
short index;
|
||
|
||
NodeIdToEntry(id, &which, &index);
|
||
result = (*(*gHistories)[which].theData)[index].label == llRemindMe;
|
||
|
||
return (result);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* LabelToString - given a history label, return the string of that
|
||
* label.
|
||
**********************************************************************/
|
||
void LabelToString(LinkLabelEnum label, Str255 dateStr)
|
||
{
|
||
GetRString(dateStr,LinkHistoryLabelsStrn+label);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* LabelableLink - return true if this is a link type we should label
|
||
**********************************************************************/
|
||
Boolean LabelableLink(LinkTypeEnum linkType)
|
||
{
|
||
Boolean result = false;
|
||
LinkTypeEnum scan;
|
||
short count = sizeof(gLabelTheseLinks)/sizeof(LinkTypeEnum);
|
||
|
||
for (scan = 0; !result && (scan < count); scan++)
|
||
{
|
||
if (gLabelTheseLinks[scan]==linkType)
|
||
result = true;
|
||
}
|
||
|
||
return (result);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* GetLHPreviewIcon - return a handle to the icon for this id.
|
||
**********************************************************************/
|
||
Handle GetLHPreviewIcon(VLNodeID id)
|
||
{
|
||
Handle theIcon = nil, rIconSuite = nil;
|
||
short which;
|
||
short index;
|
||
AdId adId;
|
||
FSSpec adGraphicSpec;
|
||
URLNameStr adGraphicName;
|
||
OSErr err = noErr;
|
||
LHPIconCacheHandle iconCache;
|
||
|
||
if (LinkHasCustomIcons())
|
||
{
|
||
// Find the history entry
|
||
NodeIdToEntry(id, &which, &index);
|
||
|
||
// Make sure it's an ad
|
||
if ((*(*gHistories)[which].theData)[index].type == ltAd)
|
||
{
|
||
adId = (*(*gHistories)[which].theData)[index].adId;
|
||
|
||
// Is the ad icon already loaded?
|
||
if (iconCache=FindPVICache(adId))
|
||
{
|
||
theIcon = (*iconCache)->theIcon;
|
||
if (theIcon && *theIcon)
|
||
{
|
||
HNoPurge(theIcon); // don't purge this again until we're done with it.
|
||
return (theIcon);
|
||
}
|
||
// else
|
||
// the icon has been purged. Read it back in.
|
||
}
|
||
|
||
// Find the icon preview file
|
||
AdIdToName(adId, adGraphicName);
|
||
if (noErr == FSMakeFSSpec(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,adGraphicName,&adGraphicSpec))
|
||
{
|
||
short iconRes, oldResFile = CurResFile();
|
||
|
||
// open the file
|
||
iconRes = FSpOpenResFile(&adGraphicSpec,fsRdPerm);
|
||
if (iconRes != -1)
|
||
{
|
||
// read in the icon
|
||
err = GetIconSuite(&rIconSuite, kCustomIconResource, svAllAvailableData);
|
||
if (rIconSuite && *rIconSuite && (err==noErr))
|
||
{
|
||
err = DupIconSuite(rIconSuite, &theIcon, false);
|
||
if (err == noErr)
|
||
{
|
||
// Remember to mark it as purgable when we're done with it!
|
||
HNoPurge(theIcon);
|
||
|
||
// Add this icon to the list of loaded icons
|
||
AddIconToPVICache(theIcon, adId);
|
||
}
|
||
}
|
||
else theIcon = nil;
|
||
|
||
CloseResFile(iconRes);
|
||
}
|
||
UseResFile(oldResFile);
|
||
}
|
||
}
|
||
}
|
||
return (theIcon);
|
||
}
|
||
|
||
/************************************************************************
|
||
* LinkUpdateCacheDate - this link was clicked on today
|
||
************************************************************************/
|
||
void LinkUpdateCacheDate(short which, short index)
|
||
{
|
||
// make sure the link histories are around
|
||
if (gHistories || (GenHistoriesList()==noErr))
|
||
{
|
||
// update the date for this TOC entry to today
|
||
(*(*gHistories)[which].theData)[index].cacheSeconds = LocalDateTime();
|
||
|
||
// save the TOC part of the file only
|
||
WriteHistTOC(which);
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* SortShortHistoryHandle - sort the monster link history handle
|
||
************************************************************************/
|
||
void SortShortHistoryHandle(ShortHistoryStructHandle toSort, int (*compare)())
|
||
{
|
||
short count = 0;
|
||
|
||
if (compare)
|
||
{
|
||
count = GetHandleSize(toSort)/sizeof(ShortHistoryStruct);
|
||
QuickSort((void*)LDRef(toSort),sizeof(ShortHistoryStruct),0,count-1,(void*)compare,(void*)SwapHist);
|
||
UL(toSort);
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* HistTypeCompare - compare two cells based on the url type
|
||
**********************************************************************/
|
||
int HistTypeCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
|
||
{
|
||
return (hist1->type - hist2->type);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* HistNameCompare - compare two cells based on the url itself
|
||
**********************************************************************/
|
||
int HistNameCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
|
||
{
|
||
return (StringComp(hist1->name,hist2->name));
|
||
}
|
||
|
||
/**********************************************************************
|
||
* HistDateCompare - compare two cells based on the cache date
|
||
**********************************************************************/
|
||
int HistDateCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
|
||
{
|
||
int result;
|
||
|
||
// does either entry have a label?
|
||
if ((hist1->label != llNone) || (hist2->label != llNone))
|
||
{
|
||
// the entry with the most important label is newest ...
|
||
result = hist1->label - hist2->label;
|
||
}
|
||
else
|
||
{
|
||
// the entry with the largest cacheSecods is newest ...
|
||
result = hist2->cacheSeconds - hist1->cacheSeconds;
|
||
}
|
||
|
||
return (result);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* HistRemindCompare - compare two cells based on the cache date.
|
||
* Special case for Remind sorting.
|
||
**********************************************************************/
|
||
int HistRemindCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
|
||
{
|
||
int result;
|
||
|
||
// does either entry have a Remind Me label?
|
||
if ((hist1->label == llRemindMe) || (hist2->label == llRemindMe))
|
||
{
|
||
// both are set to Remind Me
|
||
if ((hist1->label == llRemindMe) && (hist2->label == llRemindMe)) result = HistNameCompare(hist1, hist2);
|
||
// one is set to Remind Me
|
||
else if (hist1->label == llRemindMe) result = -1;
|
||
else result = 1;
|
||
}
|
||
else
|
||
{
|
||
// the entry with the largest cacheSecods is newest ...
|
||
result = hist2->cacheSeconds - hist1->cacheSeconds;
|
||
}
|
||
|
||
return (result);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* SwapHist - swap two history entries.
|
||
**********************************************************************/
|
||
void SwapHist(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
|
||
{
|
||
ShortHistoryStruct tempHist;
|
||
|
||
tempHist = *hist1;
|
||
*hist1 = *hist2;
|
||
*hist2 = tempHist;
|
||
}
|
||
|
||
/************************************************************************
|
||
* GetLinkURL - Return a handle to the URL of a link. (*gHistories)[which] URL should
|
||
* NOT be dumped, even after a one night stand.
|
||
************************************************************************/
|
||
Handle GetLinkURL(VLNodeInfo *info)
|
||
{
|
||
short which;
|
||
short index;
|
||
Handle hUrl;
|
||
|
||
// Figure out which file this entry is in ...
|
||
NodeIdToEntry(info->nodeID, &which, &index);
|
||
|
||
// read in the URL from the file
|
||
// Note: GetHistoryData returns a handle that belongs to the history toc struct. Don't trash it!
|
||
if ((*((*gHistories)[which].theData))[index].dirty) hUrl = GetHistoryData(which,index,false);
|
||
else hUrl = GetHistoryData(which,index,true);
|
||
|
||
return (hUrl);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AdWasClicked - call this when an Ad is clicked on in the Ad window
|
||
**********************************************************************/
|
||
void AdWasClicked(AdId adId, OSErr openErr)
|
||
{
|
||
short which, index;
|
||
|
||
if (LocateAdInHistories(adId, &which, &index))
|
||
{
|
||
// Update the label of this ad.
|
||
UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), openErr);
|
||
|
||
// Give it a new cache date when found.
|
||
LinkUpdateCacheDate(which, index);
|
||
|
||
// update the Link History window
|
||
LinkTickle();
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* LocateAdInHistories - find the ad in the histories files
|
||
**********************************************************************/
|
||
Boolean LocateAdInHistories(AdId adId, short *which, short *index)
|
||
{
|
||
short count;
|
||
Boolean foundIt = false;
|
||
short w, i;
|
||
|
||
// must have somewhere to put our results
|
||
if (!which || !index) return (false);
|
||
|
||
// Make sure the histories are around ...
|
||
if (gHistories || (GenHistoriesList()==noErr))
|
||
{
|
||
// Iterate through all History files, looking for this Ad.
|
||
for (w = 0; !foundIt && (w < NHistoryFiles); w++)
|
||
{
|
||
count = HistoryCount(w);
|
||
for (i = 0; !foundIt && (i < count); i++)
|
||
{
|
||
if (((*(*gHistories)[w].theData)[i].adId.server == adId.server)
|
||
&& ((*(*gHistories)[w].theData)[i].adId.ad == adId.ad))
|
||
{
|
||
*which = w;
|
||
*index = i;
|
||
foundIt = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return (foundIt);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* FindRemindLink - see if any links are marked as remind me
|
||
**********************************************************************/
|
||
Boolean FindRemindLink(void)
|
||
{
|
||
short count;
|
||
Boolean foundOne = false;
|
||
short w, i;
|
||
|
||
// Make sure the histories are around ...
|
||
if (gHistories || (GenHistoriesList()==noErr))
|
||
{
|
||
// Iterate through all History files
|
||
for (w = 0; !foundOne && (w < NHistoryFiles); w++)
|
||
{
|
||
count = HistoryCount(w);
|
||
for (i = 0; !foundOne && (i < count); i++)
|
||
{
|
||
if ((*(*gHistories)[w].theData)[i].remind)
|
||
foundOne = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return (foundOne);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* UnRemindLinks - forget about it!
|
||
**********************************************************************/
|
||
void UnRemindLinks(Boolean labelToo)
|
||
{
|
||
short count;
|
||
short w, i;
|
||
Boolean foundOne;
|
||
|
||
// Make sure the histories are around ...
|
||
if (gHistories || (GenHistoriesList()==noErr))
|
||
{
|
||
// Iterate through all History files
|
||
for (w = 0; (w < NHistoryFiles); w++)
|
||
{
|
||
count = HistoryCount(w);
|
||
foundOne = false;
|
||
for (i = 0; (i < count); i++)
|
||
{
|
||
if ((*(*gHistories)[w].theData)[i].remind)
|
||
{
|
||
foundOne = true;
|
||
(*(*gHistories)[w].theData)[i].remind = 0;
|
||
}
|
||
|
||
if (labelToo && ((*(*gHistories)[w].theData)[i].label==llRemindMe))
|
||
{
|
||
foundOne = true;
|
||
(*(*gHistories)[w].theData)[i].label = llBookmarked;
|
||
}
|
||
}
|
||
if (foundOne) WriteHistTOC(w);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AddAdToLinkHistory - call this to add an ad to the Link History
|
||
*
|
||
* If adURL is non-null, we're adding an ad to the history list
|
||
* if adGraphic is non-nul, we're creating a preview for the lw window
|
||
**********************************************************************/
|
||
OSErr AddAdToLinkHistory(AdId adId, StringPtr pUrl, Str255 adTitle, FSSpecPtr adGraphic)
|
||
{
|
||
OSErr err = noErr;
|
||
short which = 0, index;
|
||
ProtocolEnum protocol;
|
||
Str255 proto,host,query;
|
||
OSErr labelableErrors[] = {fnfErr};
|
||
long hashName;
|
||
Handle hUrl = nil;
|
||
URLNameStr urlName;
|
||
Boolean needSave = false;
|
||
|
||
// Add the Ad to the link History Window
|
||
if (pUrl && *pUrl && adTitle)
|
||
{
|
||
// Add the ad's URL to the link history file.
|
||
if (!(err=ParseURL(pUrl,proto,host,query)))
|
||
{
|
||
protocol = FindSTRNIndex(ProtocolStrn,proto);
|
||
FixURLString(host);
|
||
if (protocol!=proMail) FixURLString(query);
|
||
|
||
// make sure the name of the url contains something interesting
|
||
if (adTitle && adTitle[0])
|
||
{
|
||
// use the name passed in
|
||
PStrCopy(urlName, adTitle, sizeof(urlName));
|
||
}
|
||
else
|
||
{
|
||
if (host[0])
|
||
{
|
||
// use the host of the actual url as the name
|
||
PCopy(urlName, host);
|
||
}
|
||
else
|
||
{
|
||
// URL had no host. Use the actual URL
|
||
PStrCopy(urlName, pUrl, sizeof(urlName));
|
||
}
|
||
}
|
||
|
||
// The name of the history entry will be a hash of the url itself
|
||
hashName = NickHashString(pUrl);
|
||
|
||
// Turn the url into a handle we can keep around ...
|
||
hUrl = NuHTempOK(0);
|
||
if (!hUrl) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
|
||
err = PtrPlusHand(pUrl+1,hUrl,pUrl[0]);
|
||
if (err) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));
|
||
|
||
// Make sure the history files are around somewhere
|
||
err = GenHistoriesList();
|
||
|
||
// See if the ad already exists in a history file ...
|
||
if (LocateAdInHistories(adId, &which, &index))
|
||
{
|
||
// we'll need to save these changes if they were major ...
|
||
if ((*(*gHistories)[which].theData)[index].incompleteAd) needSave = true;
|
||
|
||
// update it with the new history information
|
||
(*(*gHistories)[which].theData)[index].hashName = hashName;
|
||
//(*(*gHistories)[which].theData)[index].cacheSeconds = LocalDateTime(); // - leave the date alone jdboyd 2/9/01
|
||
(*(*gHistories)[which].theData)[index].dirty = true;
|
||
(*(*gHistories)[which].theData)[index].incompleteAd = false;
|
||
(*(*gHistories)[which].theData)[index].hUrl = hUrl;
|
||
PCopy((*(*gHistories)[which].theData)[index].name,urlName);
|
||
}
|
||
else
|
||
{
|
||
// add the ad as a new history entry
|
||
if (err == noErr)
|
||
{
|
||
// Add the history to the history file
|
||
err = AddHistoryToTOC(MAIN_HISTORY_FILE, urlName, hashName, ltAd, llNotDisplayed, false, hUrl, adId);
|
||
if (err == noErr) needSave = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add the graphic to the Link History, if all has gone well
|
||
if ((err == noErr) && adGraphic && LinkHasCustomIcons())
|
||
{
|
||
// Is the ad graphic a valid graphic file?
|
||
if (FSpFileSize(adGraphic) > 0)
|
||
{
|
||
// Make sure the history files are loaded ...
|
||
err = GenHistoriesList();
|
||
|
||
if (err == noErr)
|
||
{
|
||
// Create an icon out of the graphic, save it in the History folder.
|
||
if (CreateIconFromAdGraphic(adId, adGraphic) == noErr)
|
||
{
|
||
// find the ad this graphic belongs to
|
||
if (!LocateAdInHistories(adId, &which, &index))
|
||
{
|
||
// it does not exist. Add it now, using a bogus name and hashname
|
||
err = AddHistoryToTOC(MAIN_HISTORY_FILE, "\p", -1, ltAd, llNotDisplayed, true, hUrl, adId);
|
||
}
|
||
else
|
||
{
|
||
// (*gHistories)[which] history entry now has a thumbnail ...
|
||
(*(*gHistories)[which].theData)[index].thumb = true;
|
||
}
|
||
if (err == noErr) needSave = true;
|
||
}
|
||
}
|
||
}
|
||
// else
|
||
// the ad graphic file was 0 bytes in length. Nothing to do, we'll get called later when it's downloaded.
|
||
}
|
||
|
||
// save the history file
|
||
if (needSave)
|
||
{
|
||
(*gHistories)[which].dirty = true;
|
||
err = SaveIndHistoryFile(which);
|
||
|
||
// Update the Link History window
|
||
LinkTickle();
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AgeLinks - go through history files, throw out old links.
|
||
* Warning: This is expensive!
|
||
**********************************************************************/
|
||
void AgeLinks(void)
|
||
{
|
||
short which;
|
||
short totalRemoved = 0;
|
||
static long lastAged;
|
||
|
||
// Nothing to do if there are no history files ...
|
||
if (gHistories==nil) return;
|
||
|
||
// Only do this once every hour ...
|
||
if (lastAged == 0) lastAged = TickCount();
|
||
else
|
||
{
|
||
if ((TickCount() - lastAged) < AGE_INTERVAL)
|
||
return;
|
||
}
|
||
|
||
for (which = 0; which < NHistoryFiles; which++)
|
||
totalRemoved += AgeHistoryFile(which);
|
||
|
||
// refresh the Link History window if we removed any entries
|
||
if (totalRemoved > 0)
|
||
LinkTickle();
|
||
|
||
// Trash orphaned link history previews
|
||
PurgeLinkHistoryPreviewOrphans();
|
||
|
||
// Let's not do this anytime soon ...
|
||
lastAged = TickCount();
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AgeHistoryFile - go through a history file, throw out old links
|
||
**********************************************************************/
|
||
short AgeHistoryFile(short which)
|
||
{
|
||
short count = HistoryCount(which);
|
||
short i;
|
||
long age;
|
||
long maxAge = 60*60*24*GetRLong(LINK_AGE);
|
||
long today;
|
||
short removed = 0;
|
||
|
||
// when is today?
|
||
today = GMTDateTime();
|
||
|
||
// Go through each history entry ...
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
// skip deleted entries. They are already gone ...
|
||
if ((*(*gHistories)[which].theData)[i].deleted == 0)
|
||
{
|
||
//
|
||
// Age Ads only if they're not on the current playlist
|
||
// Treat ads differently only for adware users that *have* a playlist.
|
||
//
|
||
|
||
if (IsAdwareMode() && (*(*gHistories)[which].theData)[i].type == ltAd)
|
||
{
|
||
if (IsAdInPlaylist((*(*gHistories)[which].theData)[i].adId))
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Throw out any links older than a LINK_AGE days
|
||
//
|
||
|
||
age = today - (*(*gHistories)[which].theData)[i].cacheSeconds;
|
||
if (age > maxAge)
|
||
{
|
||
DeleteHistEntryFromTOC(which, i);
|
||
removed++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Save the history TOC if we removed anything.
|
||
if (removed > 0) WriteHistTOC(which);
|
||
|
||
return (removed);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* PurgeLinkHistoryPreviewOrphans - iterate through the Link History
|
||
* folder, and trash preview files no longer in use.
|
||
**********************************************************************/
|
||
void PurgeLinkHistoryPreviewOrphans(void)
|
||
{
|
||
URLNameStr name;
|
||
CInfoPBRec hfi;
|
||
short count = 1;
|
||
AdId adId;
|
||
short which, index;
|
||
|
||
hfi.hFileInfo.ioNamePtr = name;
|
||
hfi.hFileInfo.ioFDirIndex = 0;
|
||
while (!DirIterate(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,&hfi))
|
||
{
|
||
if (hfi.hFileInfo.ioFlFndrInfo.fdType==LINK_HISTORY_PREVIEW_TYPE && hfi.hFileInfo.ioFlFndrInfo.fdCreator==CREATOR)
|
||
{
|
||
// Found a preview icon. Figure out the name of the file
|
||
if (NameToAdId(name, &adId))
|
||
{
|
||
// Is this ad on in any history file?
|
||
if (!LocateAdInHistories(adId, &which, &index))
|
||
{
|
||
// It's not. Delete thje graphic from the folder.
|
||
if (DeleteAdGraphic(adId)==noErr)
|
||
hfi.hFileInfo.ioFDirIndex--;
|
||
//else
|
||
//error deleting it. Skip it, try it next time we purge.
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* CreateIconFromAdGraphic - given an Ad, create a file with an icon
|
||
* representing the ad.
|
||
**********************************************************************/
|
||
OSErr CreateIconFromAdGraphic(AdId adId, FSSpecPtr adGraphic)
|
||
{
|
||
OSErr err = noErr;
|
||
URLNameStr graphicName;
|
||
FSSpec adIconSpec;
|
||
|
||
// The name of the graphic file will be the adId.
|
||
AdIdToName(adId, graphicName);
|
||
|
||
// See if the graphic exists already;
|
||
if (FSMakeFSSpec(gLinkHistoryFolder.vRefNum, gLinkHistoryFolder.parID, graphicName, &adIconSpec)!=noErr)
|
||
{
|
||
// the icon does not yet exist. Create it from the adGraphic file.
|
||
err = IconFromAd(&adIconSpec, adGraphic);
|
||
}
|
||
else err = dupFNErr;
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AdIdToName - given an AdId, return the name of its preview file
|
||
**********************************************************************/
|
||
void AdIdToName(AdId adId, URLNameStr name)
|
||
{
|
||
Str255 scratch;
|
||
|
||
scratch[0] = 0;
|
||
name[0] = 0;
|
||
|
||
NumToString(adId.server,name);
|
||
NumToString(adId.ad,scratch);
|
||
PCat(name,"\p,");
|
||
PCat(name,scratch);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* NameToAdId - given a name, return the id of the ad it belongs to
|
||
**********************************************************************/
|
||
Boolean NameToAdId(URLNameStr name, AdId *ad)
|
||
{
|
||
Boolean result = false;
|
||
PStr scan;
|
||
Str255 scratch;
|
||
|
||
ad->server = ad->ad = 0;
|
||
|
||
if (name && name[0])
|
||
{
|
||
// the name is <server,ad>
|
||
scan = PPtrFindSub("\p,", name, name[0]);
|
||
if (scan)
|
||
{
|
||
// figure out the server id.
|
||
PStrCopy(scratch,name,scan - name);
|
||
StringToNum(scratch, &(ad->server));
|
||
|
||
// figure out the ad id.
|
||
scratch[0] = name[0] - (scan - name);
|
||
BlockMove(scan+1,scratch+1,scratch[0]);
|
||
StringToNum(scratch, &(ad->ad));
|
||
|
||
result = true;
|
||
}
|
||
};
|
||
|
||
return (result);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* DeleteAdGraphic - given an AdId, delete its preview file
|
||
**********************************************************************/
|
||
OSErr DeleteAdGraphic(AdId adId)
|
||
{
|
||
OSErr err = noErr;
|
||
FSSpec adGraphicSpec;
|
||
URLNameStr adGraphicName;
|
||
|
||
// locate the ad file in the Link History Folder
|
||
AdIdToName(adId, adGraphicName);
|
||
err = FSMakeFSSpec(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,adGraphicName,&adGraphicSpec);
|
||
if (err == noErr)
|
||
{
|
||
// Delete the ad preview we've found.
|
||
err = FSpDelete(&adGraphicSpec);
|
||
|
||
// Remove the icon handle from the icon cache
|
||
RemoveIconFromPVICache(adId);
|
||
}
|
||
// else
|
||
// the ad preview can't be found. Too bad.
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* AddIconToPVICache - remember a loaded ad preview icon
|
||
**********************************************************************/
|
||
void AddIconToPVICache(Handle theIcon, AdId adId)
|
||
{
|
||
LHPIconCacheHandle newPVI;
|
||
|
||
newPVI = NewZH(LHPIconCacheStruct);
|
||
if (newPVI)
|
||
{
|
||
(*newPVI)->theIcon = theIcon;
|
||
(*newPVI)->adId = adId;
|
||
LL_Queue(gPreviewIcons, newPVI, (LHPIconCacheHandle));
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* FindPVICache - find an icon in the preview icon cache
|
||
**********************************************************************/
|
||
LHPIconCacheHandle FindPVICache(AdId adId)
|
||
{
|
||
LHPIconCacheHandle scan;
|
||
|
||
for (scan = gPreviewIcons; scan && (((*scan)->adId.server != adId.server) || ((*scan)->adId.ad != adId.ad)); scan = (*scan)->next);
|
||
|
||
return (scan);
|
||
}
|
||
|
||
|
||
/**********************************************************************
|
||
* RemoveIconFromPVICache - remove an icon from the cache
|
||
**********************************************************************/
|
||
void RemoveIconFromPVICache(AdId adId)
|
||
{
|
||
LHPIconCacheHandle scan;
|
||
|
||
// Locate the cache entry associated with this adId.
|
||
for (scan = gPreviewIcons; scan; scan = (*scan)->next)
|
||
{
|
||
if (((*scan)->adId.server == adId.server) && ((*scan)->adId.ad == adId.ad))
|
||
{
|
||
RemovePVIFromPVICache(&scan);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* RemovePVIFromPVICache - remove a PVI cache entry frm the list
|
||
**********************************************************************/
|
||
void RemovePVIFromPVICache(LHPIconCacheHandle *toRemove)
|
||
{
|
||
if (gPreviewIcons && toRemove && *toRemove)
|
||
{
|
||
// remove it from the list
|
||
LL_Remove(gPreviewIcons,*toRemove,(LHPIconCacheHandle));
|
||
|
||
// nuke the icon cache
|
||
DisposeIconSuite((**toRemove)->theIcon,true);
|
||
|
||
// and now the cache entry
|
||
ZapHandle(*toRemove);
|
||
*toRemove = nil;
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* ZapPVICache = destroy the icon cache
|
||
**********************************************************************/
|
||
void ZapPVICache(void)
|
||
{
|
||
LHPIconCacheHandle scan = gPreviewIcons, next = nil;
|
||
|
||
while (scan)
|
||
{
|
||
next = (*scan)->next;
|
||
RemovePVIFromPVICache(&scan);
|
||
scan = next;
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* IconFromAd - create a file containing an icon representing the
|
||
* picture in the adSpec. This assumes the adSpec is a valid file.
|
||
**********************************************************************/
|
||
OSErr IconFromAd(FSSpecPtr iconSpec, FSSpecPtr adSpec)
|
||
{
|
||
OSErr err = noErr;
|
||
GraphicsImportComponent importer;
|
||
MatrixRecord whatIsTheMatrix;
|
||
GWorldPtr gWorld = nil;
|
||
short depth = 8; // Listview supports 8 bit icons ...
|
||
Rect r,srcRect;
|
||
|
||
// figure out which importer can open the ad picture file ...
|
||
err = GetGraphicsImporterForFile(adSpec, &importer);
|
||
if ((err == noErr))
|
||
{
|
||
// Create an offscreen GWorld to do the deed in ...
|
||
SetRect(&r, 0 ,0, 32, 32);
|
||
err = NewGWorld(&gWorld, depth, &r, nil, nil, useTempMem); // Try temp memory first
|
||
if (err) err = NewGWorld(&gWorld, depth, &r, nil, nil, nil); // Failed, use application heap
|
||
if (err == noErr)
|
||
{
|
||
// (jp) 12-5-99 Many of the graphics importer routines actually can (and do) return
|
||
// errors. For example, if for what ever reason, we have a blank ad (as I currently have)
|
||
// and which could conceivably happen if we didn't have enough memory to display an ad in
|
||
// the ad window, GraphicsImportGetBoundsRect and GraphicsImportDraw return errors which
|
||
// results in a "TV snow" icon. I added more error checking... which means that legitimate
|
||
// errors really might be returned.
|
||
|
||
// Tell the importer where to put it ...
|
||
err= GraphicsImportSetGWorld(importer, gWorld, nil);
|
||
if (!err) {
|
||
// Tell the importer how to scale the picture when drawn ...
|
||
err = GraphicsImportGetBoundsRect(importer, &srcRect);
|
||
}
|
||
if (!err) {
|
||
RectMatrix(&whatIsTheMatrix, &srcRect, &r);
|
||
err = GraphicsImportSetMatrix(importer, &whatIsTheMatrix);
|
||
}
|
||
if (!err)
|
||
err = GraphicsImportDraw(importer);
|
||
|
||
// Now make an icon out of this GWorld ...
|
||
if (!err)
|
||
err = MakeLHIconFile(gWorld, &r, iconSpec, importer, adSpec);
|
||
else {
|
||
// We'll still need to put something reasonable in the icon...
|
||
// How about the gray logo pict? Not pretty, but somthing for now...
|
||
PicHandle pic;
|
||
if (pic = GetResource_('PICT',GRAY_LOGO_PICT)) {
|
||
PushGWorld();
|
||
SetGWorld(gWorld,nil);
|
||
HNoPurge_(pic);
|
||
DrawPicture(pic,&r);
|
||
HPurge((Handle)pic);
|
||
err = MakeLHIconFile(gWorld, &r, iconSpec, importer, adSpec);
|
||
PopGWorld();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Cleanup
|
||
CloseComponent(importer);
|
||
}
|
||
|
||
// Cleanup
|
||
if (gWorld) DisposeGWorld(gWorld);
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* MakeLHIconFile - create file containing a 32*32 bit icon in 1 and
|
||
* 8 bits.
|
||
**********************************************************************/
|
||
OSErr MakeLHIconFile(GWorldPtr gWorld, Rect *pRect, FSSpecPtr iconSpec, GraphicsImportComponent importer, FSSpecPtr sourceSpec)
|
||
{
|
||
OSErr err = noErr;
|
||
FInfo info;
|
||
short iconRes;
|
||
short oldResFile = CurResFile();
|
||
RGBColor color,*transparentColor=nil;
|
||
|
||
//
|
||
// First, see if this icon file already exists
|
||
//
|
||
|
||
if (noErr==FSpExists(iconSpec)) return noErr;
|
||
|
||
|
||
//
|
||
// Create a new res file to store the icons in.
|
||
//
|
||
|
||
FSpCreateResFile(iconSpec,CREATOR,LINK_HISTORY_PREVIEW_TYPE,smSystemScript);
|
||
if ((err=ResError())!=noErr)
|
||
{
|
||
return (err);
|
||
}
|
||
|
||
iconRes = FSpOpenResFile(iconSpec,fsRdWrPerm);
|
||
if (((err=ResError())!=noErr) || (iconRes ==-1))
|
||
{
|
||
FSpDelete(iconSpec);
|
||
return (err);
|
||
}
|
||
|
||
//
|
||
// Now create and save the icons
|
||
//
|
||
if (GetPNGTransColor(importer,sourceSpec,&color))
|
||
transparentColor = &color; // Use PNG transparent color when setting up mask
|
||
|
||
err = MakeIconSuite (gWorld, pRect, transparentColor, "\p");
|
||
|
||
CloseResFile(iconRes);
|
||
UseResFile(oldResFile);
|
||
|
||
|
||
//
|
||
// set the icon of the new file to the custom icon
|
||
//
|
||
|
||
if (err == noErr)
|
||
{
|
||
FSpGetFInfo(iconSpec,&info);
|
||
info.fdFlags |= kHasCustomIcon;
|
||
FSpSetFInfo(iconSpec,&info);
|
||
}
|
||
|
||
// If there was an error, forget about it
|
||
if (err)
|
||
{
|
||
FSpDelete(iconSpec);
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
|
||
OSErr MakeIconSuite (GWorldPtr gWorld, Rect *pRect, RGBColor *transparentColor, PStr name)
|
||
|
||
{
|
||
Handle icon = nil;
|
||
OSErr err = noErr;
|
||
|
||
icon = MakeIcon(gWorld,pRect,1,16); // make 16x16 1-bit color icon
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'SICN', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeICN_pound(gWorld, pRect, 32, transparentColor); // create 32x32 1-bit color icon AND MASK
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'ICN#', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeICN_pound(gWorld, pRect, 16, transparentColor); // create 16x16 1-bit color icon AND MASK
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'ics#', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeIcon(gWorld,pRect,8,32); // make 32x32 8-bit color icon
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'icl8', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeIcon(gWorld,pRect,4,32); // make 32x32 4-bit color icon
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'icl4', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeIcon(gWorld,pRect,8,16); // make 16x16 8-bit color mask
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'ics8', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
icon = MakeIcon(gWorld,pRect,4,16); // make 16x16 4-bit color mask
|
||
if(icon)
|
||
{
|
||
AddResource( icon, 'ics4', kCustomIconResource, name);
|
||
err = err || ResError();
|
||
}
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* MakeICN_pound - create a one bit icon and mask from gwp
|
||
**********************************************************************/
|
||
Handle MakeICN_pound(GWorldPtr gwp, Rect *srcRect, short iconDimension, RGBColor *transparentColor)
|
||
{
|
||
GWorldPtr scaledGWorld;
|
||
Rect scaledIcon;
|
||
Handle icon = nil;
|
||
Handle iconMask = nil;
|
||
Size iconSize;
|
||
OSErr theError;
|
||
|
||
scaledGWorld = nil;
|
||
// If we're making a scaled icon with transparency (one that is not of the same dimensions as the source rect),
|
||
// we'll have to create a second GWorld of the proper size in order to correctly create the icon mask.
|
||
if (transparentColor && RectWi (*srcRect) != iconDimension) {
|
||
SetRect (&scaledIcon, 0, 0, iconDimension, iconDimension);
|
||
theError = NewGWorld (&scaledGWorld, 8, &scaledIcon, nil, nil, useTempMem); // Try temp memory first
|
||
if (theError)
|
||
theError = NewGWorld (&scaledGWorld, 8, &scaledIcon, nil, nil, nil); // Failed, use application heap
|
||
if (!theError) {
|
||
PushGWorld ();
|
||
SetGWorld (scaledGWorld, nil);
|
||
EraseRect (&scaledIcon);
|
||
CopyBits(GetPortBitMapForCopyBits(gwp),
|
||
GetPortBitMapForCopyBits(scaledGWorld),
|
||
srcRect,
|
||
&scaledIcon,
|
||
srcCopy | ditherCopy, nil);
|
||
PopGWorld ();
|
||
}
|
||
}
|
||
|
||
icon = MakeIcon(gwp, srcRect, 1, iconDimension);
|
||
if(icon)
|
||
{
|
||
if (transparentColor)
|
||
{
|
||
// Set every bit in mask except for transparent color
|
||
if (transparentColor && RectWi (*srcRect) != iconDimension)
|
||
iconMask = MakeIconLo(scaledGWorld, &scaledIcon, 1, iconDimension, transparentColor);
|
||
else
|
||
iconMask = MakeIconLo(gwp, srcRect, 1, iconDimension, transparentColor);
|
||
}
|
||
else
|
||
iconMask = MakeIconMask(gwp, srcRect, iconDimension);
|
||
if(iconMask)
|
||
{
|
||
iconSize = GetHandleSize(icon);
|
||
SetHandleSize(icon, iconSize + GetHandleSize(iconMask));
|
||
if (MemError()==noErr) BlockMove(*iconMask, (Ptr)(((long)*icon) + iconSize), GetHandleSize(iconMask));
|
||
DisposeHandle(iconMask);
|
||
}
|
||
}
|
||
|
||
if (scaledGWorld)
|
||
DisposeGWorld (scaledGWorld);
|
||
|
||
return (icon);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* MakeIconMask - create an icon mask for an ad preview. This is
|
||
* just a black box as big as the srcRect.
|
||
**********************************************************************/
|
||
Handle MakeIconMask(GWorldPtr srcGWorld, Rect *srcRect, short iconSize)
|
||
{
|
||
Handle iconBits;
|
||
OSErr err = noErr;
|
||
GWorldPtr gWorld = nil;
|
||
PixMapHandle hPM;
|
||
RGBColor saveBackColor;
|
||
RGBColor blackColor = { 0,0,0 };
|
||
|
||
// Create an offscreen graphics world ...
|
||
err = NewGWorld(&gWorld, 1, srcRect, nil, nil, useTempMem); // Try temp memory first
|
||
if (err) err = NewGWorld(&gWorld, 1, srcRect, nil, nil, nil); // Failed, use application heap
|
||
if (err == noErr)
|
||
{
|
||
// draw a 32x32 black box
|
||
PushGWorld();
|
||
SetGWorld(gWorld,nil);
|
||
|
||
hPM = GetGWorldPixMap(gWorld);
|
||
LockPixels(hPM);
|
||
|
||
GetBackColor(&saveBackColor);
|
||
RGBBackColor(&blackColor);
|
||
EraseRect(srcRect);
|
||
RGBBackColor(&saveBackColor);
|
||
|
||
UnlockPixels(hPM);
|
||
PopGWorld();
|
||
|
||
// Make an icon out of it ...
|
||
iconBits = MakeIcon(gWorld, srcRect, 1, iconSize);
|
||
|
||
}
|
||
|
||
// Cleanup
|
||
if (gWorld) DisposeGWorld(gWorld);
|
||
|
||
return (iconBits);
|
||
}
|
||
|
||
/*
|
||
* These routines are taken from the MakeIcon sample code
|
||
*/
|
||
Handle MakeIcon(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize)
|
||
{
|
||
return MakeIconLo(srcGWorld, srcRect, dstDepth, iconSize, nil);
|
||
}
|
||
|
||
Handle MakeIconLo(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize, RGBColor *transColor)
|
||
/*
|
||
Creates a handle to the image data for an icon, or nil if an error.
|
||
The source image is specified by GWorld and regtangle defining the area
|
||
to create the icon from.
|
||
The type of icon is specified by the depth and Size paramters.
|
||
iconSize is used for both height and width.
|
||
For example, to create an Icl8 pass 8 for dstDepth and 32 for iconSize.
|
||
to create an ics8 pass 8 for the dstDepth and 16 for iconSize.
|
||
to create an ICON pass 1 for the dstDepth and 32 for iconSize.
|
||
|
||
|
||
*/
|
||
{
|
||
long bytesPerRow;
|
||
long imageSize;
|
||
Handle dstHandle;
|
||
PixMapHandle pix;
|
||
Rect iconFrame;
|
||
QDErr err;
|
||
|
||
PushGWorld(); // save Graphics env state
|
||
|
||
SetGWorld(srcGWorld,nil);
|
||
|
||
iconFrame.top = 0;
|
||
iconFrame.left = 0;
|
||
iconFrame.bottom = iconSize;
|
||
iconFrame.right = iconSize;
|
||
|
||
// make a gworld for the icl resource
|
||
pix = (PixMapHandle)NewHandleClear(sizeof(PixMap));
|
||
|
||
/* See Tech Note #120 - for info on creating a PixMap by hand as SetUpPixMap
|
||
does. SetUpPixMap was taken from that Tech Note....
|
||
*/
|
||
err = SetUpPixMap(dstDepth,&iconFrame,GetCTable(dstDepth),pix);
|
||
|
||
if(err)
|
||
{
|
||
PopGWorld();
|
||
return nil;
|
||
}
|
||
|
||
LockPixels(GetGWorldPixMap(srcGWorld));
|
||
LockPixels(pix);
|
||
|
||
if (transColor)
|
||
{
|
||
long transIdx = Color2Index(transColor);
|
||
short row,col;
|
||
RGBColor color;
|
||
unsigned char mask;
|
||
Ptr pBitMap;
|
||
short offset;
|
||
|
||
// Set bit in mask for every pixel not same color as transparency color
|
||
pBitMap = (*pix)->baseAddr;
|
||
offset = 0;
|
||
mask = 0x80;
|
||
for(row=0;row<iconSize;row++)
|
||
{
|
||
for(col=0;col<iconSize;col++)
|
||
{
|
||
GetCPixel(srcRect->left+col,srcRect->top+row,&color);
|
||
if (Color2Index(&color)!=transIdx)
|
||
// Not transparent. Set this one
|
||
pBitMap[offset] |= mask;
|
||
mask >>= 1;
|
||
if (!mask)
|
||
{
|
||
offset++;
|
||
mask = 0x80;
|
||
}
|
||
}
|
||
pBitMap += (*pix)->rowBytes & 0x3fff;
|
||
offset = 0;
|
||
mask = 0x80;
|
||
}
|
||
}
|
||
else
|
||
CopyBits(GetPortBitMapForCopyBits(srcGWorld),
|
||
(BitMapPtr)*pix,
|
||
srcRect,
|
||
&iconFrame,
|
||
srcCopy | ditherCopy, nil);
|
||
|
||
|
||
UnlockPixels(GetGWorldPixMap(srcGWorld));
|
||
|
||
bytesPerRow = ((dstDepth * ((**pix).bounds.right - (**pix).bounds.left) + 15) / 16) * 2;
|
||
imageSize = (bytesPerRow) * ((**pix).bounds.bottom - (**pix).bounds.top);
|
||
|
||
dstHandle = NewHandle(imageSize);
|
||
err = MemError ();
|
||
if(err || dstHandle == nil)
|
||
{
|
||
PopGWorld();
|
||
return nil;
|
||
}
|
||
HLock((Handle)dstHandle);
|
||
BlockMove(GetPixBaseAddr(pix),*dstHandle,imageSize);
|
||
HUnlock(dstHandle);
|
||
UnlockPixels(pix);
|
||
TearDownPixMap(pix);
|
||
|
||
PopGWorld(); // Restore graphics env to previous state
|
||
|
||
HNoPurge(dstHandle);
|
||
return dstHandle;
|
||
}
|
||
|
||
void FreeBitMap(BitMap *Bits)
|
||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
Dumps a BitMap created by NewBitMap below.
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||
{
|
||
DisposePtr(Bits->baseAddr);
|
||
Bits->baseAddr=nil;
|
||
SetRect(&Bits->bounds,0,0,0,0);
|
||
Bits->rowBytes=0;
|
||
}
|
||
|
||
void CalcOffScreen(register Rect *frame,register long *needed, register short *rows)
|
||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
Calculates how much memory and rowbytes a bitmap of the size frame would require.
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||
{
|
||
*rows=((((frame->right) - (frame->left)) + 15)/16) *2;
|
||
*needed=(((long)(*rows)) * (long)((frame->bottom) - (frame->top)));
|
||
}
|
||
|
||
void NewBitMap(Rect *frame,BitMap *theMap)
|
||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
Creates a new empty bitmap.
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||
{
|
||
Size size;
|
||
short rbytes;
|
||
|
||
CalcOffScreen(frame,&size,&rbytes);
|
||
|
||
theMap->rowBytes=rbytes;
|
||
theMap->bounds=*frame;
|
||
theMap->baseAddr=NewPtrClear(size);
|
||
}
|
||
|
||
void NewMaskedBitMap(BitMap *srcBits, BitMap *maskBits, Rect *srcRect)
|
||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
Pass a pointer to an existing bitmap, and this creates a bitmap that is an
|
||
equivelent mask.
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||
{
|
||
short rowbytes,height,words;
|
||
long needed;
|
||
|
||
NewBitMap(srcRect,maskBits);
|
||
|
||
if(MemError())
|
||
{
|
||
maskBits->baseAddr=nil;
|
||
SetRect(&maskBits->bounds,0,0,0,0);
|
||
maskBits->rowBytes=0;
|
||
}
|
||
else /* memory was allocated for Mask BitMap ok */
|
||
{
|
||
CalcOffScreen(srcRect,&needed,&rowbytes);
|
||
words=rowbytes/2;
|
||
height=srcRect->bottom - srcRect->top;
|
||
CalcMask(srcBits->baseAddr,maskBits->baseAddr,rowbytes,rowbytes,height,words);
|
||
}
|
||
}
|
||
|
||
#define kDefaultRes 0x00480000 /* Default resolution is 72 DPI; Fixed type */
|
||
|
||
OSErr SetUpPixMap(
|
||
short depth, /* Desired number of bits/pixel in off-screen */
|
||
Rect *bounds, /* Bounding rectangle of off-screen */
|
||
CTabHandle colors, /* Color table to assign to off-screen */
|
||
PixMapHandle aPixMap /* Handle to the PixMap being initialized */
|
||
)
|
||
{
|
||
CTabHandle newColors; /* Color table used for the off-screen PixMap */
|
||
Ptr offBaseAddr; /* Pointer to the off-screen pixel image */
|
||
OSErr error; /* Returns error code */
|
||
short bytesPerRow; /* Number of bytes per row in the PixMap */
|
||
|
||
|
||
error = noErr;
|
||
newColors = nil;
|
||
offBaseAddr = nil;
|
||
|
||
bytesPerRow = ((depth * (bounds->right - bounds->left) + 15) / 16) * 2;
|
||
|
||
/* Clone the clut if indexed color; allocate a dummy clut if direct color*/
|
||
if (depth <= 8)
|
||
{
|
||
newColors = colors;
|
||
// error = HandToHand((Handle *) &newColors); // This doesn't appear to be necessary and creates a memory leak alb 2/00
|
||
}
|
||
else
|
||
{
|
||
newColors = (CTabHandle) NewHandle(sizeof(ColorTable) -
|
||
sizeof(CSpecArray));
|
||
error = MemError();
|
||
}
|
||
if (error == noErr)
|
||
{
|
||
/* Allocate pixel image; long integer multiplication avoids overflow */
|
||
offBaseAddr = NewPtrClear((unsigned long) bytesPerRow * (bounds->bottom -
|
||
bounds->top));
|
||
if (offBaseAddr != nil)
|
||
{
|
||
/* Initialize fields common to indexed and direct PixMaps */
|
||
(**aPixMap).baseAddr = offBaseAddr; /* Point to image */
|
||
(**aPixMap).rowBytes = bytesPerRow | /* MSB set for PixMap */
|
||
0x8000;
|
||
(**aPixMap).bounds = *bounds; /* Use given bounds */
|
||
// (**aPixMap).pmVersion = 0; /* No special stuff */
|
||
// (**aPixMap).packType = 0; /* Default PICT pack */
|
||
// (**aPixMap).packSize = 0; /* Always zero in mem */
|
||
(**aPixMap).hRes = kDefaultRes; /* 72 DPI default res */
|
||
(**aPixMap).vRes = kDefaultRes; /* 72 DPI default res */
|
||
(**aPixMap).pixelSize = depth; /* Set # bits/pixel */
|
||
// (**aPixMap).planeBytes = 0; /* Not used */
|
||
// (**aPixMap).pmReserved = 0; /* Not used */
|
||
|
||
/* Initialize fields specific to indexed and direct PixMaps */
|
||
if (depth <= 8)
|
||
{
|
||
/* PixMap is indexed */
|
||
// (**aPixMap).pixelType = 0; /* Indicates indexed */
|
||
(**aPixMap).cmpCount = 1; /* Have 1 component */
|
||
(**aPixMap).cmpSize = depth; /* Component size=depth */
|
||
(**aPixMap).pmTable = newColors; /* Handle to CLUT */
|
||
}
|
||
else
|
||
{
|
||
/* PixMap is direct */
|
||
(**aPixMap).pixelType = RGBDirect; /* Indicates direct */
|
||
(**aPixMap).cmpCount = 3; /* Have 3 components */
|
||
if (depth == 16)
|
||
(**aPixMap).cmpSize = 5; /* 5 bits/component */
|
||
else
|
||
(**aPixMap).cmpSize = 8; /* 8 bits/component */
|
||
(**newColors).ctSeed = 3 * (**aPixMap).cmpSize;
|
||
// (**newColors).ctFlags = 0;
|
||
// (**newColors).ctSize = 0;
|
||
(**aPixMap).pmTable = newColors;
|
||
}
|
||
}
|
||
else
|
||
error = MemError();
|
||
}
|
||
else
|
||
newColors = nil;
|
||
|
||
/* If no errors occured, return a handle to the new off-screen PixMap */
|
||
if (error != noErr)
|
||
{
|
||
if (newColors != nil)
|
||
DisposeCTable(newColors);
|
||
}
|
||
|
||
/* Return the error code */
|
||
return error;
|
||
}
|
||
|
||
void TearDownPixMap(PixMapHandle pix)
|
||
{
|
||
DisposeCTable((*pix)->pmTable);
|
||
DisposePtr((*pix)->baseAddr);
|
||
DisposeHandle((Handle)pix);
|
||
}
|