/* 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 "linkwin.h"

#define FILE_NUM 126
/* Copyright (c) 1999 by QUALCOMM Incorporated */

#pragma segment LinkWin

#define NUM_COLUMNS 3

#define THUMB_COLUMN 0
#define NAME_COLUMN 1
#define DATE_COLUMN 2

#define MINI_BANNER_WIDTH 70

// Link History window controls
enum
{
	kctlColumns1=0,
	kctlColumns2,
	kctlColumns3,
	kctlView,
	kctlRemove
};

typedef struct
{
	ViewList list;
	MyWindowPtr win;
	ControlHandle ctlColumns[NUM_COLUMNS],ctlView,ctlRemove;
	Boolean	inited;
	Boolean needsSort;
	short columnLefts[NUM_COLUMNS];
	LinkSortTypeEnum sort;
} WinData;
static WinData gWin;

short gColumnHeaderControls[NUM_COLUMNS] = {LINK_THUMB_COL_CNTL,LINK_NAME_COL_CNTL,LINK_DATE_COL_CNTL};

/************************************************************************
 * prototypes
 ************************************************************************/
// Link window
static void DoDidResize(MyWindowPtr win, Rect *oldContR);
static void DoZoomSize(MyWindowPtr win,Rect *zoom);
static void DoUpdate(MyWindowPtr win);
static Boolean DoClose(MyWindowPtr win);
static void DoClick(MyWindowPtr win,EventRecord *event);
static void DoCursor(Point mouse);
static void DoActivate(MyWindowPtr win);
static Boolean DoKey(MyWindowPtr win, EventRecord *event);
static OSErr DoDragHandler(MyWindowPtr win,DragTrackingMessage which,DragReference drag);
static void DoShowHelp(MyWindowPtr win,Point mouse);
static Boolean DoMenuSelect(MyWindowPtr win, int menu, int item, short modifiers);
static long ViewListCallBack(ViewListPtr pView, VLCallbackMessage message, long data);
static Boolean GetListData(VLNodeInfo *data,short selectedItem);
static void SetControls(void);
static void DoGrow(MyWindowPtr win,Point *newSize);
static Boolean LinkFind(MyWindowPtr win,PStr what);
static Boolean LinkDrawRowCallBack(ViewListPtr pView,short item,Rect *pRect,CellRec *pCellData,Boolean select,Boolean eraseName);
static Boolean LinkFillBlankCallback(ViewListPtr pView);
static void OpenSelectedLinks(void);
static void DeleteSelectedLinks(void);
static void GetCellRectsForLHWin(ViewListPtr pView,CellRec *pCellData,Rect *pRect,Rect *pIcon,Rect *pName,Rect *pDate,Point *pNamePt,Point *pDatePt);
static Boolean LinkAddColumnHeaders(void);
static void LinkPositionColumnHeaders(MyWindowPtr win,short count,ControlHandle *pCtlList,short left,short right,short top,short height);
static short ColumnRight(short column, short max);
static void DoColumnClick(MyWindowPtr win, EventRecord *event, ControlHandle ctl);
static Boolean LinkGetCellRectsCallBack(ViewListPtr pView, CellRec *cellData, Rect *cellRect, Rect *iconRect, Rect *nameRect);
static Boolean LinkInterestingClickCallBack(ViewListPtr pView, CellRec *cellData, Rect *cellRect, Point pt);
static void SetLinkWinBackgroundColor(void);
static LinkSortTypeEnum LinkSavedSortOrder(void);
static void LinkSaveSortOrder(LinkSortTypeEnum sort);

// Offline Link Dialog
typedef enum
{
	kOfflineLinkDlogNow = 1,
	kOfflineLinkDlogCancel = 2,
	kOfflineLinkDlogBookmark = 3,
	kOfflineLinkDlogRemind = 4,
	kOfflineLinkDlogRemember = 5,
	kOfflineLinkDlogRemindText = 8,
	kOfflineLinkDlogText = 10
} OfflineLinkEnum;

enum
{
	kRemindDlogNow = 1,
	kRemindDlogLater,
	kRemindDlogForget
};

#define VaildOfflineActionPref(p) ((p == kOfflineLinkDlogNow) || (p == kOfflineLinkDlogBookmark) || (p == kOfflineLinkDlogRemind))

OfflineLinkEnum DoOfflineLinkDialog(void);
void DoRemindNag(void);
OSErr RemindDlogInit (DialogPtr theDialog, long refcon);
Boolean RemindDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon);
void RemindSortLinkWin(void);
Boolean RemindUserNow(void);
Boolean CanRemindUser(void);

/************************************************************************
 * OpenLinkWin - open the link window
 ************************************************************************/
void OpenLinkWin(void)
{
	DrawDetailsStruct details;
	WindowPtr					gWinWinPtr;
	
	if (!HasFeature (featureLink))
		return;

	UseFeature (featureLink);
		
	if (SelectOpenWazoo(LINK_WIN)) return;	//	Already opened in a wazoo

	if (!gWin.inited)
	{
		short err=0;
		Rect r;
		
		if (GenHistoriesList()!=noErr) goto fail;
		
		// age the links if we haven't in a while ...
		AgeLinks();
			
		if (!(gWin.win=GetNewMyWindow(LINK_WIND,nil,nil,BehindModal,false,false,LINK_WIN)))
			{err=MemError(); goto fail;}
		gWinWinPtr = GetMyWindowWindowPtr (gWin.win);
		SetWinMinSize(gWin.win,320,12*FontLead);
		SetPort_(GetMyWindowCGrafPtr(gWin.win));
		ConfigFontSetup(gWin.win);	
		MySetThemeWindowBackground(gWin.win,kThemeListViewBackgroundBrush,False);

		/*
		 * list
		 */
		
		SetRect(&r,0,0,20,20);
		
		details.arrowLeft = 2;
		details.iconTop = 2;
		details.iconLeft = details.arrowLeft;	//	First icon level
		details.iconLevelWidth = 0;
		details.textBottom = 5;
		details.rowHt = 36;
		details.nameAddMargin = 5;		//	Add to name width when drawing
		details.maxNameWidth = 225;		//	Approximate maximum name width
		details.keyNavTicks = 60;		//	Delay accepted between navigation keys
		
		// callbacks for list drawing
		details.DrawRowCallback = LinkDrawRowCallBack;
		details.FillBlankCallback = LinkFillBlankCallback;
		details.GetCellRectsCallBack = LinkGetCellRectsCallBack;
		details.InterestingClickCallback = LinkInterestingClickCallBack;
	
		gWin.sort = LinkSavedSortOrder();
		gWin.needsSort = true;
			
		if (LVNewWithDetails(&gWin.list,gWin.win,&r,1,ViewListCallBack,flavorTypeText,&details))
			{err=MemError(); goto fail;}
		
		// controls
		if (!(gWin.ctlView = NewIconButton(LINK_NEW_CNTL,gWinWinPtr)) ||
			!(gWin.ctlRemove = NewIconButton(LINK_REMOVE_CNTL,gWinWinPtr)) ||
				!(LinkAddColumnHeaders()))
				goto fail;

		// columns.  Do this in a more flexible way someday
		gWin.columnLefts[THUMB_COLUMN] = 0;
		gWin.columnLefts[NAME_COLUMN] = MINI_BANNER_WIDTH+2;
		gWin.columnLefts[DATE_COLUMN] = gWin.columnLefts[NAME_COLUMN] + details.maxNameWidth;
		
		gWin.win->didResize = DoDidResize;
		gWin.win->close = DoClose;
		gWin.win->update = DoUpdate;
		gWin.win->position = PositionPrefsTitle;
		gWin.win->click = DoClick;
		gWin.win->bgClick = DoClick;
		gWin.win->dontControl = true;
		gWin.win->cursor = DoCursor;
		gWin.win->activate = DoActivate;
		gWin.win->help = DoShowHelp;
		gWin.win->menu = DoMenuSelect;
		gWin.win->key = DoKey;
		gWin.win->app1 = DoKey;
		gWin.win->drag = DoDragHandler;
		gWin.win->zoomSize = DoZoomSize;
		gWin.win->grow = DoGrow;
		gWin.win->idle = nil;
		gWin.win->find = LinkFind;
		gWin.win->showsSponsorAd = true;
			
		MyWindowDidResize(gWin.win,&gWin.win->contR);	
		ShowMyWindow(gWinWinPtr);
		gWin.inited = true;
		return;
		
		fail:
			if (gWin.win) CloseMyWindow(GetMyWindowWindowPtr(gWin.win));
			if (err) WarnUser(COULDNT_WIN,err);
			return;
	}
	UserSelectWindow(GetMyWindowWindowPtr(gWin.win));
}

/************************************************************************
 * LinkAddColumnHeaders - get the column header controls for the LH win
 ************************************************************************/
static Boolean LinkAddColumnHeaders(void)
{
	WindowPtr	gWinWinPtr = GetMyWindowWindowPtr (gWin.win);
	Boolean result = true;
	short i;
	
	for (i = 0; result && (i < NUM_COLUMNS); i++)
	{
		if (!(gWin.ctlColumns[i] = NewIconButton(gColumnHeaderControls[i],gWinWinPtr)))
			result = false;
	}
	
	return (result);
}

/************************************************************************
 * DoDidResize - resize the window
 ************************************************************************/
static void DoDidResize(MyWindowPtr win, Rect *oldContR)
{
#define kListInset 10 
#pragma unused(oldContR)
	ControlHandle	ctlList[NUM_COLUMNS];
	Rect r;
	short htAdjustment;
	short column;
	short columnHeaderHeight = GROW_SIZE+5;

	// column header controls
	for (column = 0; column < NUM_COLUMNS; column++) ctlList[column] = gWin.ctlColumns[column];
	LinkPositionColumnHeaders(win,NUM_COLUMNS,ctlList,kListInset,gWin.win->contR.right-kListInset,win->topMargin+kListInset,columnHeaderHeight);
	
	//	buttons
	ctlList[0] = gWin.ctlView;
	ctlList[1] = gWin.ctlRemove;
	PositionBevelButtons(win,2,ctlList,kListInset,gWin.win->contR.bottom-INSET-kHtCtl,kHtCtl,RectWi(win->contR));

	// list
	SetRect(&r,kListInset,win->topMargin+kListInset+columnHeaderHeight,gWin.win->contR.right-kListInset,gWin.win->contR.bottom - 2*INSET - kHtCtl);
	if (gWin.win->sponsorAdExists && r.bottom >= gWin.win->sponsorAdRect.top - kSponsorBorderMargin)
		r.bottom = gWin.win->sponsorAdRect.top - kSponsorBorderMargin;
	LVSize(&gWin.list,&r,&htAdjustment);
	
	//	enable/disble controls
	SetControls();
		
	// redraw
	InvalContent(win);
}

/************************************************************************
 * LinkPositionColumnHeaders - position and size the column headers
 ************************************************************************/
static void LinkPositionColumnHeaders(MyWindowPtr win,short count,ControlHandle *pCtlList,short left,short right,short top,short height)
{
	short	width[64];
	short	i;
	short 	max = right - left + (count - 1);	// rightmost part of last column header, relative to the list.
	short 	columnLeft = left;
	
	//	Get width of each column header
	for (i=0;i<count;i++)
	{
		width[i] = pCtlList[i] ? (ColumnRight(i,max)-gWin.columnLefts[i]) : 0;
	}
	
	// move the column headers
	for (i=0;i<count;i++)
	{
		if (pCtlList[i])
		{
			MoveMyCntl(pCtlList[i],columnLeft-1,top,width[i],height);
			columnLeft += width[i];
		}
	}
}


/************************************************************************
 * SetControls - enable or disable the controls, based on current situation
 ************************************************************************/
static void SetControls(void)
{
	short i,count = LVCountSelection(&gWin.list);
	Boolean fSelect = count!=0;
	LinkSortTypeEnum sortedColumn = gWin.sort&kLinkSortTypeMask;
	
	// depress sorted column control
	for (i = 0; i < NUM_COLUMNS; i++)
		SetControlValue(gWin.ctlColumns[i],(sortedColumn == i));

	// enable/disable buttons
	SetGreyControl(gWin.ctlView,!fSelect);	
	SetGreyControl(gWin.ctlRemove,!fSelect);	
}

/************************************************************************
 * DoZoomSize - zoom to only the maximum size of list
 ************************************************************************/
static void DoZoomSize(MyWindowPtr win,Rect *zoom)
{
	short zoomHi = zoom->bottom-zoom->top;
	short zoomWi = zoom->right-zoom->left;
	short hi, wi;
	
	LVMaxSize(&gWin.list, &wi, &hi);
	wi += 2*kListInset;
	hi += 2*kListInset + INSET + kHtCtl;

	wi = MIN(wi,zoomWi); wi = MAX(wi,win->minSize.h);
	hi = MIN(hi,zoomHi); hi = MAX(hi,win->minSize.v);
	zoom->right = zoom->left+wi;
	zoom->bottom = zoom->top+hi;
}

/************************************************************************
 * DoGrow - adjust grow size
 ************************************************************************/
static void DoGrow(MyWindowPtr win,Point *newSize)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	Rect	r;
	short	htControl = ControlHi(gWin.ctlView);
	short	bottomMargin,sponsorMargin;
	
	//	Get list position
	bottomMargin = INSET*2 + htControl;
	if (win->sponsorAdExists)
	{
		Rect	rPort;
		
		GetPortBounds(GetWindowPort(winWP),&rPort);
		sponsorMargin = rPort.bottom - win->sponsorAdRect.top + kSponsorBorderMargin;
		if (sponsorMargin > bottomMargin) bottomMargin  = sponsorMargin;
	}
	SetRect(&r,kListInset,win->topMargin+kListInset,newSize->h-kListInset,newSize->v - bottomMargin);
	LVCalcSize(&gWin.list,&r);
	
	//	Calculate new window height
	newSize->v = r.bottom + bottomMargin;
}

/************************************************************************
 * DoClose - close the window
 ************************************************************************/
static Boolean DoClose(MyWindowPtr win)
{
#pragma unused(win)
	
	if (gWin.inited)
	{
		//	Dispose of list
		LVDispose(&gWin.list);
		gWin.inited = false;
		
		// clean up the history list, but don't throw it away.
		ZapHistoriesList(false);
		
		// Take care of the icon cache as well
		ZapPVICache();
	}
	return(True);
}

/************************************************************************
 * DoUpdate - draw the window
 ************************************************************************/
static void DoUpdate(MyWindowPtr win)
{
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	Rect	r,rCntl0,rCntlLast;
	
	// Tweak the link history window colors
	SetLinkWinBackgroundColor();
	
	// draw the list
	r = gWin.list.bounds;
	DrawThemeListBoxFrame(&r,kThemeStateActive);
	LVDraw(&gWin.list,MyGetPortVisibleRegion(winPort), true, false);
	
	// Draw a rect around the column controls
	GetControlBounds(gWin.ctlColumns[0],&rCntl0);
	GetControlBounds(gWin.ctlColumns[NUM_COLUMNS-1],&rCntlLast);
	SetRect(&r,rCntl0.left-1,rCntl0.top-1,rCntlLast.right,rCntl0.bottom);
	FrameRect(&r);
}

/************************************************************************
 * DoActivate - activate the window
 ************************************************************************/
static void DoActivate(MyWindowPtr win)
{
#pragma unused(win)
	LVActivate(&gWin.list, gWin.win->isActive);
	SetControls();
}

/************************************************************************
 * DoKey - key stroke
 ************************************************************************/
static Boolean DoKey(MyWindowPtr win, EventRecord *event)
{
#pragma unused(win)
	short key = (event->message & 0xff);

	if (LVKey(&gWin.list,event))
	{
		SetControls();
		return true;
	}

	return false;
}

/**********************************************************************
 * LinkFind - find in the window
 **********************************************************************/
static Boolean LinkFind(MyWindowPtr win,PStr what)
{
	return FindListView(win,&gWin.list,what);
}

/************************************************************************
 * DoClick - click in window
 ************************************************************************/
void DoClick(MyWindowPtr win,EventRecord *event)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	Point pt;
	ControlHandle	hCtl;
	
	SetPort(GetMyWindowCGrafPtr(win));

	if (!LVClick(&gWin.list,event))
	{
		pt = event->where;
		GlobalToLocal(&pt);
		if (!win->isActive)
		{
			SelectWindow_(winWP);
			UpdateMyWindow(winWP);	//	Have to update manually since no events are processed
		}
		
		if (FindControl(pt, winWP, &hCtl))
		{
			if (TrackControl(hCtl,pt,(void *)(-1)))
			{
				if (hCtl == gWin.ctlView)
				{
					OpenSelectedLinks();
					AuditHit((event->modifiers&shiftKey)!=0, (event->modifiers&controlKey)!=0, (event->modifiers&optionKey)!=0, (event->modifiers&cmdKey)!=0, false, GetWindowKind(winWP), AUDITCONTROLID(GetWindowKind(winWP),kctlView), event->what);
				}
				else if (hCtl == gWin.ctlRemove)
				{
					DeleteSelectedLinks();
					AuditHit((event->modifiers&shiftKey)!=0, (event->modifiers&controlKey)!=0, (event->modifiers&optionKey)!=0, (event->modifiers&cmdKey)!=0, false, GetWindowKind(winWP), AUDITCONTROLID(GetWindowKind(winWP),kctlRemove), event->what);
				}
				else
					DoColumnClick(win, event, hCtl);
			}
		}
	}
	SetControls();
}

/************************************************************************
 * DoColumnClick - Handle a click on a column header, if indeed it is one
 ************************************************************************/
static void DoColumnClick(MyWindowPtr win, EventRecord *event, ControlHandle ctl)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	short column = 0;
	LinkSortTypeEnum oldSort = gWin.sort;
	LinkSortTypeEnum newSort;
	
	while (column < NUM_COLUMNS)
	{
		if (ctl == gWin.ctlColumns[column])
		{
			newSort = column;
			if (event->modifiers&optionKey) newSort |= kLinkReverseSort; 
			
			if (gWin.sort != newSort)
			{
				gWin.needsSort = true;
				gWin.sort = newSort;
				LinkSaveSortOrder(gWin.sort);
				LinkTickle();
			}
			AuditHit((event->modifiers&shiftKey)!=0, (event->modifiers&controlKey)!=0, (event->modifiers&optionKey)!=0, (event->modifiers&cmdKey)!=0, false, GetWindowKind(winWP), AUDITCONTROLID(GetWindowKind(winWP),column), mouseDown);
			break;
		}
		column++;
	}
}
					
/************************************************************************
 * NotifyLinkWin - notify the link window that we've been up to no good
 ************************************************************************/
void NotifyLinkWin(void)
{
	if (!HasFeature (featureLink))
		return;

	if (gWin.inited) InvalidListView(&gWin.list);	//	Regenerate list	
}

/************************************************************************
 * DoCursor - set the cursor properly for the window
 ************************************************************************/
static void DoCursor(Point mouse)
{
	if (!PeteCursorList(gWin.win->pteList,mouse))
		SetMyCursor(arrowCursor);
}

/************************************************************************
 * DoShowHelp - provide help for the window
 ************************************************************************/
static void DoShowHelp(MyWindowPtr win,Point mouse)
{
	if (PtInRect(mouse,&gWin.list.bounds))
		MyBalloon(&gWin.list.bounds,100,0,LINK_HELP_STRN+1,0,nil);
	else
		ShowControlHelp(mouse,LINK_HELP_STRN+2,gWin.ctlView,gWin.ctlRemove,gWin.ctlColumns[THUMB_COLUMN],gWin.ctlColumns[NAME_COLUMN],gWin.ctlColumns[DATE_COLUMN],nil);
}

/************************************************************************
 * GetListData - get data for selected item
 ************************************************************************/
static Boolean GetListData(VLNodeInfo *data,short selectedItem)
{
	return LVGetItem(&gWin.list,selectedItem,data,true);
}

/************************************************************************
 * DoMenuSelect - menu choice in the window
 ************************************************************************/
static Boolean DoMenuSelect(MyWindowPtr win, int menu, int item, short modifiers)
{
#pragma unused(win,modifiers)
	
	switch (menu)
	{
		case FILE_MENU:
			switch(item)
			{
				case FILE_OPENSEL_ITEM:		// this should open the selected link
//					MBListOpen();
					return(True);
					break;
			}
			break;

		case EDIT_MENU:
			if (item==EDIT_SELECT_ITEM)
			{
				if (LVSelectAll(&gWin.list))
				{
					SetControls();
					return(true);
				}
			}
			break;
	}
	return(False);
}

/**********************************************************************
 * DoDragHandler - handle drags
 **********************************************************************/
static OSErr DoDragHandler(MyWindowPtr win,DragTrackingMessage which,DragReference drag)
{	
#pragma unused(win)
	OSErr err = noErr;

//#warning  don't forget to handle drags from link history window!
	
	if (!DragIsInteresting(drag,MESS_FLAVOR,TOC_FLAVOR,nil))
		return(dragNotAcceptedErr);	//	Nothing here we want
	
	switch (which)
	{
		case kDragTrackingEnterWindow:
		case kDragTrackingLeaveWindow:
		case kDragTrackingInWindow:
				err = LVDrag(&gWin.list,which,drag);
			break;
		case 0xfff:	
			break;
	}
	return err;
}

/************************************************************************
 * LinkFillBlankCallback - callback function for List View.  Fill unused 
 *	parts of the list.
 ************************************************************************/
static Boolean LinkFillBlankCallback(ViewListPtr pView)
{
	ListHandle	hList=pView->hList;
	Rect	rErase;
	GrafPtr	savePort;
	short column;
	Rect columnRect;
	LinkSortTypeEnum sortedColumn = gWin.sort&kLinkSortTypeMask;
	
	if ((*hList)->visible.bottom > (*hList)->dataBounds.bottom)
	{
		SAVE_STUFF;

		SET_COLORS;
		GetPort(&savePort);
		SetPort(GetMyWindowCGrafPtr(pView->wPtr));
		Cell1Rect(hList,(*hList)->dataBounds.bottom+1,&rErase);
		rErase.bottom = pView->bounds.bottom;

		if (UseListColors)
		{					
			for (column = 0; column < NUM_COLUMNS; column++)
			{	
				// what is the rect describing this column?
				SetRect(&columnRect,rErase.left+gWin.columnLefts[column],rErase.top,rErase.left+ColumnRight(column,rErase.right),rErase.bottom);
				
				// color this column
				SetThemeBackground(sortedColumn==column ? kThemeBrushListViewSortColumnBackground:kThemeBrushListViewBackground,RectDepth(&columnRect),true);
				EraseRect(&columnRect);
			}
		}
			
		SetPort(savePort);
		REST_STUFF;
	} 
	return true;	/* mtc sez - this value is never checked 11-18-03 */
}

/************************************************************************
 * ColumnRight - return the right side of a column
 ************************************************************************/
static short ColumnRight(short column, short max)
{
	short right = 0;
	
	if ((column>=0) && (column<NUM_COLUMNS))
	{
		if (column < NUM_COLUMNS - 1) 
			right = gWin.columnLefts[column+1];
		else 
			right = max;
	}
	
	return (right);
}

/************************************************************************
 * LinkDrawRowCallBack - callback function for List View.  Draw a row.
 ************************************************************************/
static Boolean LinkDrawRowCallBack(ViewListPtr pView,short item,Rect *pRect,CellRec *pCellData,Boolean select,Boolean eraseName)
{
	Boolean result = true;
	Rect rIcon, rName, rDate;
	Point ptName, ptDate;
	Boolean hasColor = IsColorWin(GetMyWindowWindowPtr(pView->wPtr));
	Str255 dateStr;
	short column;
	Rect columnRect;
	Handle theIcon;
	LinkSortTypeEnum sortedColumn = gWin.sort&kLinkSortTypeMask;
	
	if (item && item <= (*pView->hList)->dataBounds.bottom)
	{	
		SAVE_STUFF;
		
		//
		// Figure out where things are in this row
		//
		
		GetCellRectsForLHWin(pView,pCellData,pRect,&rIcon,&rName,&rDate,&ptName,&ptDate);


		//
		//	Draw the list background color and seperator lines for this row
		//
		
		if (UseListColors)
		{				
			for (column = 0; column < NUM_COLUMNS; column++)
			{	
				// what is the rect describing this column?
				SetRect(&columnRect,pRect->left+gWin.columnLefts[column],pRect->top,pRect->left+ColumnRight(column,pRect->right),pRect->top + (*pView->hList)->cellSize.v - 1);
				
				// color this column
				if (sortedColumn==column || ((sortedColumn == sSpecialRemind) && (IsMarkedRemind(pCellData->nodeID))))
					SetThemeBackground(kThemeBrushListViewSortColumnBackground,RectDepth(&columnRect),true);
				else
					SetThemeBackground(kThemeBrushListViewBackground,RectDepth(&columnRect),true);
				EraseRect(&columnRect);
			}
			
			SET_COLORS;
		}
	
		//
		// 	Draw the contents of this item
		//
		
		SET_COLORS;
		
		//	Plot the icon
		if (pCellData->iconID)
		{
			switch (pCellData->iconID)
			{
				case kCustomIconResource:
					theIcon = GetLHPreviewIcon(pCellData->nodeID);
					if (theIcon)
					{
						PlotIconSuite(&rIcon, atNone + atHorizontalCenter, select ? ttSelected : ttNone, theIcon);
						HPurge(theIcon);	// This icon can be purged when needed.
						break;
					}
					else
					{
						// this ad does not have a preview for some reason
						pCellData->iconID = HTTP_LINK_TYPE_ICON;
						
						// fall through and plot the http icon
					}
				
				default:
					OffsetRect(&rIcon,0,-1);
					PlotIconID(&rIcon,atNone + atHorizontalCenter,select ? ttSelected : ttNone,pCellData->iconID);
					break;
			}
		}
		
		//	Draw the name
		MoveTo(ptName.h,ptName.v);
		if (eraseName)
			EraseRect(&rName);
		if (pCellData->misc.style)
			TextFace(pCellData->misc.style);

		TextFont(pView->font);
		TextSize(pView->fontSize);

		DrawString(pCellData->name);
		if (pCellData->misc.style)
			TextFace(0);

		if (select)
			InvertRect(&rName);

		// Draw the date
		if (GetDateString(pCellData->nodeID,dateStr))
		{
			MoveTo(ptDate.h,ptDate.v);
			if (eraseName)
				EraseRect(&rDate);
			if (pCellData->misc.style)
				TextFace(pCellData->misc.style);

			TextFont(pView->font);
			TextSize(pView->fontSize);

			DrawString(dateStr);
			if (pCellData->misc.style)
				TextFace(0);
		}
							
		REST_STUFF;
	}
	
	return (result);
}

/**********************************************************************
 * GetCellRects - get triangle, icon, and name rects
 **********************************************************************/
static void GetCellRectsForLHWin(ViewListPtr pView,CellRec *pCellData,Rect *pRect,Rect *pIcon,Rect *pName,Rect *pDate,Point *pNamePt,Point *pDatePt)
{
	short	offset;
	Point	pt;
	FontInfo	fInfo;
	short column;
	
	SAVE_STUFF;
	
	for (column = 0; column < NUM_COLUMNS; column++)
	{
		switch (column)
		{
			case THUMB_COLUMN:
				//	Thumb or Icon
				if (pCellData->iconID)
				{
					// This entry has an icon.  Return rect of icon centered in thumb column.
					
					if (column < NUM_COLUMNS - 1)
					{
						// center of column, minus half of an icon
						offset = (gWin.columnLefts[column+1] - gWin.columnLefts[column])/2 - 16;	
					}
					else
					{
						// half an icon away from the leftmost part of the column
						offset = gWin.columnLefts[column] + 48;		
					}	
					SetRect(pIcon,pRect->left+offset,pRect->top+(pView->details.iconTop),pRect->left+offset+32,pRect->top+(pView->details.iconTop)+32);
				}
				else
				{
					// This entry has a thumbnail.
					SetRect(pIcon,pRect->left+(pView->details.arrowLeft),pRect->top+(pView->details.iconTop),pRect->left+(pView->details.arrowLeft)+62,pRect->top+(pView->details.iconTop)+32);
				}
				break;
				
			case NAME_COLUMN:
			case DATE_COLUMN:
				offset = gWin.columnLefts[column] + (pView->details.nameAddMargin);
				TextFont(pView->font);
				TextSize(pView->fontSize);
				pt.h = pRect->left + offset;
				pt.v = pRect->top+((*pView->hList)->cellSize.v/2)+(pView->fontSize)/2;	// center the text vertically
				GetFontInfo(&fInfo);
				
				if (column==NAME_COLUMN)
				{
					*pNamePt = pt;
					SetRect(pName,pt.h,pt.v-fInfo.ascent-fInfo.leading,pt.h + StringWidth(pCellData->name),pt.v+fInfo.descent);
				}
				else if (column==DATE_COLUMN)
				{
					*pDatePt = pt;
					SetRect(pDate,pt.h,pt.v-fInfo.ascent-fInfo.leading,pt.h + StringWidth(pCellData->name),pt.v+fInfo.descent);
				}
				break;
				
			default:
				break;
		}
	
	}
	

	
	REST_STUFF;
}

/************************************************************************
 * ViewListCallBack - callback function for List View
 ************************************************************************/
static long ViewListCallBack(ViewListPtr pView, VLCallbackMessage message, long data)
{
	VLNodeInfo	*pInfo;
	OSErr	err = noErr;
	Handle	hUrl;
	SendDragDataInfo	*pSendData;

	switch (message)
	{
		case kLVAddNodeItems:
			AddAllHistoryItems(pView,gWin.needsSort,gWin.sort);		// Add History items to the list.  Sort if necessary.
			break;
		
		case kLVOpenItem:
			OpenSelectedLinks();
			break;
			
		case kLVDeleteItem:
			DeleteSelectedLinks();
			break;
		
		case kLVRenameItem:
			break;
		
		case kLVQueryItem:
			pInfo = (	VLNodeInfo *)data;
			switch (pInfo->query)
			{
				case kQuerySelect:
				case kQueryDrag:
				case kQueryDCOpens:
					return true;
				
				case kQueryDrop:
				case kQueryRename:
					return false;
			}
			break;

		case kLVSendDragData:
			pSendData = (SendDragDataInfo*)data;
			hUrl = GetLinkURL(pSendData->info);
			HLock(hUrl);
			err = SetDragItemFlavorData(pSendData->drag, pSendData->itemRef, pSendData->flavor, *hUrl, InlineGetHandleSize((Handle)hUrl), 0L);
			UL(hUrl);
			break;
	}
	return err;
}

/************************************************************************
 * OpenSelectedLinks - open all selected links
 ************************************************************************/
static void OpenSelectedLinks(void)
{
	short i;
	VLNodeInfo info;
	
	for (i=1;i<=LVCountSelection(&gWin.list);i++)
	{				
		GetListData(&info,i);
		OpenHistoryEntry(&info);
	}
}

/************************************************************************
 * DeleteSelectedLinks - delete all selected links
 ************************************************************************/
static void DeleteSelectedLinks(void)
{
	short i;
	VLNodeInfo info;
	
	for (i=1;i<=LVCountSelection(&gWin.list);i++)
	{				
		GetListData(&info,i);
		DeleteHistoryEntry(&info);
	}
	
	// Save all history files that need it
	SaveAllHistoryFiles();
	
	// Tell the Link Window to redraw itself.
	LinkTickle();
}
			
/************************************************************************
 * LinkTickle - tickle the link window
 ************************************************************************/
void LinkTickle(void)
{		
	if (gWin.inited)
	{
		InvalidListView(&gWin.list);
		UpdateMyWindow(GetMyWindowWindowPtr(gWin.win));
	}
}

/************************************************************************
 * LinkInterestingClickCallBack - return true if this point is interesting
 ************************************************************************/
static Boolean LinkInterestingClickCallBack(ViewListPtr pView, CellRec *cellData, Rect *cellRect, Point pt)
{
	Boolean result = false;
	Rect rIcon, rName, rDate;
	Point ptName, ptDate;
	
	GetCellRectsForLHWin(pView,cellData,cellRect,&rIcon,&rName,&rDate,&ptName,&ptDate);
	if (PtInRect(pt,&rIcon) || PtInRect(pt,&rName) || PtInRect(pt,&rDate)) result = true;
	
	return (result);
}

/**********************************************************************
 * GetCellData - Get data for cell,item is 0-based
 **********************************************************************/
static short GetCellData(ViewListPtr pView,short item,CellRec *pCellData)
{
	short	dataLen;
	Cell	c;
	
	dataLen = sizeof(CellRec);
	c.h = 0; c.v = item;
	LGetCell((Ptr)pCellData, &dataLen, c, pView->hList);
	return dataLen;
}

/************************************************************************
 * LinkGetCellRectsCallBack - return true if this we should drag
 ************************************************************************/
static Boolean LinkGetCellRectsCallBack(ViewListPtr pView, CellRec *cellData, Rect *cellRect, Rect *iconRect, Rect *nameRect)
{
	Rect rDate;
	Point ptName, ptDate;

	GetCellRectsForLHWin(pView,cellData,cellRect,iconRect,nameRect,&rDate,&ptName,&ptDate);
	
	return (true);
}

/************************************************************************
 * SetLinkWinBackgroundColor - set the background color of the link win
 ************************************************************************/
static void SetLinkWinBackgroundColor(void)
{
	if (UseListColors) 
	{
		// Using finder list color scheme.  Punt on background color.
		gWin.win->backColor.red = gWin.win->backColor.green = gWin.win->backColor.blue = 0xffff;
	}
	else
	{
		// Using BACK_COLOR.
		GetRColor(&gWin.win->backColor,BACK_COLOR);	
	}
}

/************************************************************************
 * LinkHasCustomIcons - return if we should draw preview icons
 ************************************************************************/
Boolean LinkHasCustomIcons(void)
{
	long flags = GetRLong(LINK_HISTORY_UI_FLAGS);
	
	return ((flags&kLinkHasNoCustomIcons)==0);
}

/************************************************************************
 * LinkSavedSortOrder - return sort order saved from last time
 ************************************************************************/
static LinkSortTypeEnum LinkSavedSortOrder(void)
{
	long flags = GetRLong(LINK_HISTORY_UI_FLAGS);
	
	if (flags & kLinkHasNoCustomIcons) flags &= ~kLinkHasNoCustomIcons;
	
	return (flags);
}

/************************************************************************
 * LinkSaveSortOrder - remember the sort order
 ************************************************************************/
static void LinkSaveSortOrder(LinkSortTypeEnum sort)
{	
	long newFlags = sort;
	
	if (!LinkHasCustomIcons()) newFlags |= kLinkHasNoCustomIcons;
	
	SetPrefLong(LINK_HISTORY_UI_FLAGS,newFlags);
}

/************************************************************************
 * PreventOfflineLink - don't follow this link if we're offline
 ************************************************************************/
Boolean PreventOfflineLink(OSErr *err, Boolean justDoIt)
{
	Boolean preventConnection = false;
	Boolean goingToFail = false;
	long offlineAction = 0;
	
	*err = noErr;
	
	// Only makes sense to do this if the Link Window is present
	if (!HasFeature(featureLink)) return (false);
	
	if (!justDoIt)
	{
		// don't follow this link if we're offline
		goingToFail = Offline;
	
		// this link will fail if we have to dial
		if (!goingToFail)
		{
			if (!CanCheckPPPState()) return (false);	// if we can't tell if we're offline, pretend we're online
			goingToFail = TCPWillDial(true);
		}
	}
	
	// if the machine is running OT, we know for sure if the link is going to fail or not now.
	if (goingToFail || justDoIt)
	{
		// did the user tell us what to do last time?  If not, ask again.
		offlineAction = GetRLong(PREF_OFFLINE_LINK_ACTION);
		if (!VaildOfflineActionPref(offlineAction) || PrefIsSet(PREF_LINK_OFFLINE_WARN))
			offlineAction = DoOfflineLinkDialog();

		// if the user cancels the dialog, prevent the connection.
		if (offlineAction ==  kOfflineLinkDlogCancel)
		{
			// we won't do anything
			*err = olaCancel;
			
			// prevent the connection ...
			preventConnection = true;
		}

		// connect now
		if (offlineAction == kOfflineLinkDlogNow)
		{
			// connect to the internet right now ...
			*err = OTConnectForLink();
			if (*err != noErr) preventConnection = true;
		}
		
		// bookmark
		if (offlineAction == kOfflineLinkDlogBookmark)
		{
			// we will mark the link in the Link History window ...
			*err = oldaBookmark;
			
			// and not connect right now ...
			preventConnection = true;
		}
		
		// remind later
		if (offlineAction == kOfflineLinkDlogRemind)
		{
			// we will remember this link ...
			*err = oldaRemind;
			
			// and not connect right now ...
			preventConnection = true;
		}
	}
	// else
		// no need to prevent the connection.
				
	return (preventConnection);
}

#ifdef	NOT_NEEDED
/************************************************************************
 * PingAdServer - can we see the ad server?
 *
 *	Do this before launching an ad or web link.  This will either cause
 * the OS to dial, which will happen anyway when the web browser comes
 * up, or produce some sort of error.
 *
 *	If there's an error, we know we're not online, and we can display the
 * Offline Link Dialog.
 ************************************************************************/
Boolean PingAdServer(void)
{
	Boolean result = false;
	TransStream stream = nil;
	extern char PlayListURL[];	// currently in adwin.c
	Str255 proto,host,query;
	unsigned char *p;
	long port = 80;	//default http: port
	OSErr err = NewTransStream(&stream);
	Boolean oldPref = PrefIsSet(PREF_IGNORE_PPP);
	
//#warning Ping the Adserver, whatever we're supposed to
	if (err == noErr)
	{
		// extract the host from the playlist url
		if ((err=ParseURL(PlayListURL,proto,host,query))==noErr)
		{
			if (host[0])
			{
				// null terminate host ...
				host[MIN(host[0]+1,255)] = 0;
					
				// figure out the server and port from the hostname ...
				p = PIndex(host, ':');
				if (p) 
				{
					host[0] = p - host - 1;	// host now contains the hostname only;
					p++;
					if (port = atoi(p));	// port is whatever's left over
					else port = 80;			// or the default http: port
				}
				
				// try to connect to the host or something.  See if it's there.
				SetPref(PREF_IGNORE_PPP,YesStr);
				err = ConnectTrans(stream, host, port, true, GetRLong(OFFLINE_LINK_OPEN_TIMEOUT));
				SetPref(PREF_IGNORE_PPP,oldPref ? YesStr : NoStr);

				// That's all we wanted.  Cleanup.
				if (err == noErr) 
				{
					DestroyTrans(stream);
					result = true;
				}
				ZapTransStream(&stream);

			}
		}
	}
		
	return (result);
}
#endif	//NOT_NEEDED

/************************************************************************
 * DoOfflineLinkDialog - Ask the user what to do with a link that can't
 *	be followed right now.
 ************************************************************************/
OfflineLinkEnum DoOfflineLinkDialog(void)
{
	MyWindowPtr	offlineLinkDlogWin;
	DialogPtr offlineLinkDlog;
	extern ModalFilterUPP DlgFilterUPP;
	short itemHit;
	Boolean done;
	short itemType;
	Handle itemH;
	Rect rect;
	Boolean remember;
	
	SAVE_PORT;
		
	//get the Offline Link dialog
	SetDialogFont (SmallSysFontID());
	offlineLinkDlogWin = GetNewMyDialog(OFFLINE_LINK_DLOG,nil,nil,InFront);	
	SetDialogFont(0);

	offlineLinkDlog = GetMyWindowDialogPtr (offlineLinkDlogWin);
	
	if (!offlineLinkDlog)
	{
		WarnUser(GENERAL,MemError());
		return(kOfflineLinkDlogCancel);
	}	

	//fix the controls
	SetPort(GetDialogPort(offlineLinkDlog));
	ConfigFontSetup(offlineLinkDlogWin);
//	ReplaceAllControls(offlineLinkDlog);			// let's leave the checkbox alone ...

	// uncheck the Remember checkbox
	remember = false;
	GetDialogItem(offlineLinkDlog, kOfflineLinkDlogRemember, &itemType, &itemH, &rect);
	SetDItemState(offlineLinkDlog,kOfflineLinkDlogRemember,remember);
	
	// hide the Remind stuff if we're too uninformed
	if (!CanRemindUser())
	{
		HideDialogItem(offlineLinkDlog,kOfflineLinkDlogRemind);
		HideDialogItem(offlineLinkDlog,kOfflineLinkDlogRemindText);
	}

	//Do the dialog
	SetMyCursor(arrowCursor);
	StartMovableModal(offlineLinkDlog);
	ShowWindow(GetMyWindowWindowPtr (offlineLinkDlogWin));
	
	done = false;
	do
	{
		MovableModalDialog(offlineLinkDlog,DlgFilterUPP,&itemHit);
		switch(itemHit)
		{
			case kOfflineLinkDlogRemember:
				remember = !remember;
				GetDialogItem(offlineLinkDlog, kOfflineLinkDlogRemember, &itemType, &itemH, &rect);
				SetDItemState(offlineLinkDlog,kOfflineLinkDlogRemember,remember);
				break;
			
			case kOfflineLinkDlogNow:
			case kOfflineLinkDlogBookmark:
			case kOfflineLinkDlogRemind:
			case kOfflineLinkDlogCancel:
				done = true;
				break;
				
			default:
				break;
		}			
	} while (!done);

	// Set the remember pref properly if we were supposed to ...
	if (remember && VaildOfflineActionPref(itemHit))
	{
		SetPrefLong(PREF_OFFLINE_LINK_ACTION,itemHit);
		SetPref(PREF_LINK_OFFLINE_WARN, NoStr);
	}
	else
	{
		SetPrefLong(PREF_OFFLINE_LINK_ACTION,0);
		SetPref(PREF_LINK_OFFLINE_WARN, YesStr);
	}
		
	EndMovableModal(offlineLinkDlog);
	DisposDialog_(offlineLinkDlog);
	
	REST_PORT;
	
	return (itemHit);
}

/************************************************************************
 * RemindNag - Nag the user about links remembered in the Link Window
 ************************************************************************/
void RemindNag(void)
{	
	// Are there any links to remind the user about?
	if (gNeedRemind)
	{
		// nothing to do if the Link Window isn't around.
		if (!HasFeature(featureLink)) return;
		
		if (!CanCheckPPPState()) return;
		
		// see if we're online.
		if (RemindUserNow())
		{			
			// Nag the user
			DoRemindNag();
			
			// don't need to do remind again anytime soon.
			gNeedRemind = false;
		}
	}
}

/************************************************************************
 * DoRemindNag - display the remind nag
 ************************************************************************/
void DoRemindNag(void)
{
	extern ModalFilterUPP DlgFilterUPP;
		
	Nag(NAG_LINK_REMIND_DLOG, RemindDlogInit, RemindDlogHit, nil, false, 0);
}

/************************************************************************
 * RemindDlogInit - init the Remind nag
 ************************************************************************/
OSErr RemindDlogInit (DialogPtr theDialog, long refcon)
{
	OSErr theError = noErr;
	
	ConfigFontSetup(GetDialogMyWindowPtr(theDialog));
	
	return (theError);
}

/************************************************************************
 * RemindDlogHit - handle clicks in the remind dialog
 ************************************************************************/
Boolean RemindDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
{
	switch (itemHit) 
	{
		// remind the user about the sites NOW
		case kRemindDlogNow:
			CloseMyWindow (GetDialogWindow(theDialog));
			RemindSortLinkWin();
			UnRemindLinks(false);
			break;
		
		// make the dialog go away, but don't forget about the sites
		case kRemindDlogLater:
			CloseMyWindow (GetDialogWindow(theDialog));
			break;
			
		// forget about the sites completely
		case kRemindDlogForget:
			UnRemindLinks(true);
			CloseMyWindow (GetDialogWindow(theDialog));
			LinkTickle();
			break;
	}
	return (true);
}

/************************************************************************
 * RemindSortLinkWin - sort the link window so the Remind links filter
 *	to the top.
 ************************************************************************/
void RemindSortLinkWin(void)
{
	OpenLinkWin();

	if (gWin.inited)
	{
		gWin.needsSort = true;
		gWin.sort = sSpecialRemind;
		LinkSaveSortOrder(gWin.sort);
		ScrollTopListView(&(gWin.list));
		SetControls();
		LinkTickle();
	}
}

/************************************************************************
 * RemindUserNow - are we online enough to remind the user about links?
 *	This is a lot like AutoCheckOK().
 ************************************************************************/
Boolean RemindUserNow(void)
{
	uLong hourBits;
	DateTimeRec dtr;
	
	// if we're offline, forget about it
	if (Offline) return(False);

	// if we're on batteries, and we're not supposed to check while on batteries, don't fire up the network for this either.
	// go ahead and remind the user if the network is up, even if the machine is on batteries. -jdboyd 01/22/01
//	if (PrefIsSet(PREF_NO_BATT_CHECK) && OnBatteries()) return(False);
	
	// if PPP is down, forget it.
	if (PPPDown()) return(False);
	
	// are we outside the range of time we're allowed to check?  Don't remind the user now, either
	hourBits = GetPrefLong(PREF_CHECK_HOURS);
	if (0!=(hourBits&kHourUseMask) || 0!=(hourBits&kHourNeverWeekendMask))
	{
		SecondsToDate(LocalDateTime(),&dtr);
		if (dtr.dayOfWeek==1||dtr.dayOfWeek==7)	// weekends
		{
			if (hourBits&kHourAlwaysWeekendMask) return(True);
			if (hourBits&kHourNeverWeekendMask) return(False);
		}
		if (0!=(hourBits&kHourUseMask))	// non-weekends, or weekend same as weekday
		{
			return 0!=(hourBits&(1L<<dtr.hour));
		}
	}
	return(True);
}

/************************************************************************
 * CanRemindUser - we can only reliably remind the user if OT/PPP or
 *	MacSLIP is being used, or if we're fully offline.
 ************************************************************************/
Boolean CanRemindUser(void)
{
	// we're offline.  We'll remind the user once we're back online.
	if (Offline) return true;
	
	// we're using OT/PPP or MacSLIP to connect
	if (CanCheckPPPState()) return true;
	
	// otherwise, we're not going to be able to tell when we go back online.
	return (false);
}