1 line
52 KiB
C
Executable File
1 line
52 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. */
|
||
|
||
#ifdef NAG
|
||
#include "nag.h"
|
||
#define FILE_NUM 120
|
||
|
||
#pragma segment Nagging
|
||
|
||
#define kUpdateNagScheduleDuringBeta 0x55555555 // Update checks every other day
|
||
#define kUpdateNagIntervalDuringBeta 2
|
||
|
||
#define kDelayBeforeSwitchingRepayUsersToSponsored (60 * 60 * 60) // 60 ticks x 60 seconds x 60 minutes
|
||
long gTimeAtWhichRepayUsersBecomeSponsored = 0;
|
||
|
||
extern ModalFilterUPP DlgFilterUPP;
|
||
OSErr PutFeaturesInto (DialogPtr theDialog, short theItem);
|
||
|
||
#define NAG_INTRO_DLOG_OK 1
|
||
#define NAG_INTRO_INFO_BTN 3
|
||
#define NAG_INTRO_CODE_BTN 2
|
||
|
||
void CheckAdQT(void);
|
||
|
||
//
|
||
// InitNagging
|
||
//
|
||
// This is not a real function. It exists right now mostly to initialize the user state
|
||
// for our early betas. It should be replaced by a function that erifies the user's
|
||
// registration state. For now, the user is a newUser if the 'NagS' resource is not
|
||
// present. If it is present, we automatically make them an Ad User.
|
||
//
|
||
|
||
InitNagResultType InitNagging (void)
|
||
|
||
{
|
||
InitNagResultType result;
|
||
UserStateType newState;
|
||
uLong regDate;
|
||
int pnPolicyCode;
|
||
int regMonth;
|
||
Boolean foundRegFile,
|
||
needsRegistration;
|
||
|
||
needsRegistration = false;
|
||
|
||
// Grab the nag state information from the settings file, or create a new
|
||
// resource if we didn't fint what we wanted
|
||
|
||
nagState = GetResourceFromFile (NAG_STATE_TYPE, NAG_STATE_ID, SettingsRefN);
|
||
if (!nagState)
|
||
if (nagState = NuHandleClear (sizeof (NagStateRec))) {
|
||
(*nagState)->version = NAG_STATE_VERS;
|
||
#ifdef I_HATE_THE_BOX
|
||
(*nagState)->state = boxUser;
|
||
#else
|
||
if (ValidRegCode(paidUser,&pnPolicyCode,®Month) && PolicyCheck (pnPolicyCode, regMonth))
|
||
(*nagState)->state = paidUser;
|
||
else
|
||
(*nagState)->state = newUser;
|
||
#endif
|
||
(*nagState)->regDate = 0;
|
||
SettingsHandle (NAG_STATE_TYPE, nil, NAG_STATE_ID, nagState);
|
||
}
|
||
else
|
||
DieWithError (MEM_ERR, MemError ());
|
||
|
||
#ifdef DEATH_BUILD
|
||
newState = paidUser;
|
||
result = noDialogPending;
|
||
#else
|
||
// If we somehow happened upon an old EP 4 user, they will become adware users for 5.0
|
||
if (IsEP4User ())
|
||
#ifdef I_HATE_THE_BOX
|
||
(*nagState)->state = boxUser;
|
||
#else
|
||
(*nagState)->state = adwareUser;
|
||
#endif
|
||
|
||
|
||
#ifdef I_HATE_THE_BOX
|
||
if (!PrefIsSet (PREF_BOX_NO_LONGER_VIRGIN)) {
|
||
(*nagState)->state = boxUser;
|
||
SetPref(PREF_BOX_NO_LONGER_VIRGIN,"\py");
|
||
}
|
||
#else
|
||
// If someone ran as a box user, then switches to a non-box build, make them a paid user
|
||
// (which we eventually reg validate)
|
||
if ((*nagState)->state == boxUser)
|
||
(*nagState)->state = paidUser;
|
||
#endif
|
||
#endif
|
||
|
||
// Look for a RegCode file next to the Eudora application
|
||
regDate = (*nagState)->regDate;
|
||
#ifndef DEATH_BUILD
|
||
foundRegFile = CheckForRegCodeFile (&needsRegistration, ®Date, &pnPolicyCode);
|
||
(*nagState)->regDate = regDate;
|
||
#endif
|
||
|
||
ChangedResource ((Handle) nagState);
|
||
MyUpdateResFile (SettingsRefN);
|
||
DetachResource ((Handle) nagState);
|
||
|
||
// Choose the user's initial state from all we know of the world
|
||
#ifndef DEATH_BUILD
|
||
newState = ChooseInitialUserState ((*nagState)->state, foundRegFile, needsRegistration, pnPolicyCode, &result);
|
||
#endif
|
||
|
||
gCanPayMode = UserHasValidPaidModeRegcode();
|
||
|
||
if (newState != (*nagState)->state)
|
||
TransitionState (newState);
|
||
|
||
CheckAdQT();
|
||
return (result);
|
||
}
|
||
|
||
void DoPendingNagDialog (InitNagResultType pendingNagResult)
|
||
|
||
{
|
||
switch (pendingNagResult) {
|
||
case paymentRegPending:
|
||
OpenPayWin ();
|
||
break;
|
||
case codeEntryPending:
|
||
(void) CodeEntryForProduct (paidUser, invalidRegCodeEntryVariant);
|
||
break;
|
||
case repayPending:
|
||
case gracelessRepayPending:
|
||
RepayDialog (pendingNagResult);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// CheckNagging
|
||
//
|
||
//
|
||
// It's time to nag if there is a nag day greater than the last nag, and
|
||
// less than or equal to the current day. Nag days are stored in the 'Nag '
|
||
// resource as a bit mask corresponding to days 0 thru 31 of the nag
|
||
// schedule. We can accomplish this by a strange and non-obvious formula
|
||
// (yet somehow occurred to me one evening while watching Stephen King's
|
||
// "Storm Of The Century"):
|
||
//
|
||
// [((1 << (currentDay + 1)) - 1) - ((1 << (lastDay + 1)) - 1)] & schedule
|
||
//
|
||
// (Note that lastDay MUST be <= currentDay...)
|
||
//
|
||
// Here's how it works:
|
||
//
|
||
// Since each day since the nagBase is represented by a single bit, we
|
||
// can left shift a normalized day to represent that particular day.
|
||
// Likewise, subtracting 1 from a single shifted bit turns on all bits
|
||
// less than the set bit. Subtracting one such one-subtracted bit value
|
||
// from a second will result in setting all bits between one bit and another.
|
||
//
|
||
// For example:
|
||
//
|
||
// NagBase = 0 (always)
|
||
// lastDay = 6
|
||
// Current = 11
|
||
// Schedule = [0, 4, 9, 12, 3] Mask is 0000 0000 0000 0000 0001 0010 0001 0001
|
||
//
|
||
// Using the formula above...
|
||
//
|
||
// currentDay + 1 : 12
|
||
// left shift 1 by 12 bits : 0000 0000 0000 0000 0001 0000 0000 0000
|
||
// subtract 1 : 0000 0000 0000 0000 0000 1111 1111 1111 1st value
|
||
//
|
||
// lastDay + 1 : 7
|
||
// left shift 1 by 7 bits : 0000 0000 0000 0000 0000 0000 1000 0000
|
||
// subtract 1 : 0000 0000 0000 0000 0000 0000 0111 1111 2nd value
|
||
//
|
||
// subtract the 2nd from 1st : 0000 0000 0000 0000 0000 1111 1000 0000
|
||
//
|
||
// Which means that we'll nag if there is a nag scheduled for any of the
|
||
// days set in the results, so... let's AND it with the schedule.
|
||
//
|
||
// 0000 0000 0000 0000 0000 1111 1000 0000
|
||
// AND 0000 0000 0000 0000 0001 0010 0001 0001
|
||
// --------------------------------------------
|
||
// 0000 0000 0000 0000 0000 0010 0000 0000 NAG!!!
|
||
//
|
||
// Of course, the above only works during the days covered by the nag schedule itself.
|
||
// Once we go past this date we have to look at the nag interval to make the determination
|
||
// of whether or no we will nag.
|
||
//
|
||
|
||
void CheckNagging (UserStateType state)
|
||
|
||
{
|
||
NagRec nag;
|
||
NagUsageHandle nagUsage;
|
||
NagDialogInitProcPtr initProc;
|
||
NagDialogHitProcPtr hitProc;
|
||
ModalFilterUPP theFilterProc;
|
||
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
|
||
AdFailureRec adFailure;
|
||
#endif
|
||
OSErr theError;
|
||
long refcon;
|
||
uLong nagBaseSeconds, // In seconds
|
||
lastNagDay, // In days
|
||
currentDay, // In days
|
||
lastScheduledDay, // In days
|
||
nextNagDay, // In days
|
||
currentTime, // In seconds
|
||
currentDayMask,
|
||
lastDayMask;
|
||
short dialogID,
|
||
nagID,
|
||
index;
|
||
Boolean nagMe,
|
||
saveMe,
|
||
ignoreDefaultItem;
|
||
|
||
nagMe = false;
|
||
saveMe = false;
|
||
index = 1;
|
||
theError = noErr;
|
||
|
||
// Check to see if it's time to change repay users to sponsored mode
|
||
if (IsRepayUser () && gTimeAtWhichRepayUsersBecomeSponsored)
|
||
if (TickCount () >= gTimeAtWhichRepayUsersBecomeSponsored) {
|
||
TransitionState (RegisteredUser (paidUser) ? regAdwareUser : adwareUser);
|
||
gTimeAtWhichRepayUsersBecomeSponsored = 0;
|
||
return;
|
||
}
|
||
|
||
// Check each of the possible nag scedules for this state -- until we find it's time to nag
|
||
while (!theError && !GetIndNagState (&nag, &nagID, state, index++)) {
|
||
// Get the nag usage record for the particular nag dialog we're thinking about (as saved in settings)
|
||
nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, nagID, SettingsRefN);
|
||
|
||
// If the nag usage record is empty, create one and store it in settings
|
||
currentTime = LocalDateTime ();
|
||
if (!nagUsage)
|
||
if (nagUsage = NuHandleClear (sizeof (NagUsageRec))) {
|
||
(*nagUsage)->version = NAG_USAGE_VERS;
|
||
(*nagUsage)->nagBase = currentTime;
|
||
(*nagUsage)->lastNag = 0;
|
||
(*nagUsage)->aux = 0;
|
||
if (!SettingsHandle (NAG_USAGE_TYPE, nil, nagID, nagUsage))
|
||
MyUpdateResFile (SettingsRefN);
|
||
else
|
||
ZapHandle (nagUsage);
|
||
}
|
||
#ifdef BETA_UPDATE_SCHEDULE
|
||
if (nag.flags & nagFlagSilentUpdate) {
|
||
nag.schedule = kUpdateNagScheduleDuringBeta;
|
||
nag.interval = kUpdateNagIntervalDuringBeta;
|
||
}
|
||
#endif
|
||
|
||
if (nagUsage) {
|
||
// Normalize the dates for the nag base, last nag and the current
|
||
// day in order to calculate the days-since-nagBase
|
||
nagBaseSeconds = SecondsToWholeDay ((*nagUsage)->nagBase);
|
||
lastNagDay = (*nagUsage)->lastNag ? (SecondsToWholeDay ((*nagUsage)->lastNag) - nagBaseSeconds) / kSecondsPerDay : 0;
|
||
currentDay = (SecondsToWholeDay (currentTime) - nagBaseSeconds) / kSecondsPerDay;
|
||
|
||
// Figure out the last scheduled day (it's the most significant set bit of the schedule)
|
||
lastScheduledDay = MostSignificantSetBit (nag.schedule);
|
||
|
||
// The dialog that we _might_ nag with
|
||
dialogID = nag.dialogID;
|
||
|
||
// If the last nag day falls within the schedule, we can use our spiffy formula,
|
||
// otherwise we'll have to more laboriously test to see if the nag day falls into
|
||
// the correct range using the nag interval
|
||
// (jp) 2-5-00 Previously, we were checking to see if the last day we nagged fell within the
|
||
// range of the nag schedule. For example, in a schedule like 7,14,3 we'd be checking
|
||
// to see if the last day we nagged was less than or equal to 14 -- the last day in the
|
||
// schedule before the nag interval kicked in. This was wrong, however, and caused
|
||
// us to never again check in the current day was past the final day of the range (as was
|
||
// the case for the ad failure check). The correct algorithm is to check the current
|
||
// day against the range, use the bit mask if we're still running within the range, otherwise
|
||
// we take into account the nag interval.
|
||
// if (lastNagDay <= lastScheduledDay) {
|
||
if (currentDay <= lastScheduledDay) {
|
||
currentDayMask = (1 << (currentDay + 1)) - 1;
|
||
lastDayMask = (*nagUsage)->lastNag ? (1 << (lastNagDay + 1)) - 1 : 0;
|
||
nagMe = (currentDayMask - lastDayMask) & nag.schedule ? true : false;
|
||
}
|
||
else {
|
||
nextNagDay = lastScheduledDay + nag.interval * ((lastNagDay - lastScheduledDay) / nag.interval) + nag.interval;
|
||
nagMe = nextNagDay > lastNagDay && nextNagDay <= currentDay;
|
||
}
|
||
// If it is time to nag the user in whichever manner is appropriate -- this might be a dialog, or
|
||
// it might be a request to perform some operation. We should also update the 'NagU' resource.
|
||
if (nagMe)
|
||
if (nag.flags & nagFlagSilentUpdate && !PrefIsSet (PREF_DISABLE_AUTO_UPDATE_CHECK) && !Offline) {
|
||
#ifndef DO_NOT_UPDATE_CHECK_IN_DEATH_BUILDS
|
||
theError = UpdateCheck (true, false);
|
||
(*nagUsage)->lastNag = currentTime;
|
||
saveMe = true;
|
||
#endif
|
||
nagMe = false;
|
||
} else
|
||
if (nag.flags & nagFlagUpdate && !PrefIsSet (PREF_DISABLE_AUTO_UPDATE_CHECK) && !Offline) {
|
||
#ifndef DO_NOT_UPDATE_CHECK_IN_DEATH_BUILDS
|
||
theError = UpdateCheck (false, false);
|
||
(*nagUsage)->lastNag = currentTime;
|
||
saveMe = true;
|
||
#endif
|
||
nagMe = false;
|
||
}
|
||
else
|
||
if (nag.flags & nagFlagAdFailureCheck) {
|
||
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
|
||
// Check for ad failures and deadbeatness
|
||
LDRef (nagUsage);
|
||
adFailure.error = AdFailureCheck (nagUsage, currentTime, &nagMe, &dialogID, adFailure.text);
|
||
UL (nagUsage);
|
||
(*nagUsage)->lastNag = currentTime;
|
||
saveMe = true;
|
||
#else
|
||
nagMe = false;
|
||
#endif
|
||
}
|
||
|
||
#ifdef DEATH_BUILD
|
||
if (nagMe && dialogID == NAG_PLEASE_REGISTER_DLOG)
|
||
nagMe = false;
|
||
#endif
|
||
|
||
// If we're still nagging (no error and a flag didn't handle things), nag away!
|
||
if (!theError && nagMe) {
|
||
initProc = nil;
|
||
hitProc = nil;
|
||
theFilterProc = nil;
|
||
refcon = 0;
|
||
ignoreDefaultItem = false;
|
||
switch (dialogID) {
|
||
case NAG_INTRO_DLOG:
|
||
hitProc = IntroDlogHit;
|
||
theFilterProc = DlgFilterUPP;
|
||
break;
|
||
case NAG_PLEASE_REGISTER_DLOG:
|
||
hitProc = AdUserHit;
|
||
break;
|
||
case NAG_DOWNGRADE_DLOG:
|
||
initProc = DowngradeDlogInit;
|
||
hitProc = DowngradeDlogHit;
|
||
break;
|
||
case NAG_FEATURES_DLOG:
|
||
initProc = FeaturesDlogInit;
|
||
hitProc = FeaturesDlogHit;
|
||
break;
|
||
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
|
||
case NAG_NOT_GETTING_ADS_DLOG:
|
||
initProc = NotGettingAdsDlogInit;
|
||
hitProc = NotGettingAdsDlogHit;
|
||
refcon = (long) &adFailure;
|
||
break;
|
||
case NAG_FRIGGING_HACKER_DLOG:
|
||
hitProc = NoAdsRevertToFreeDlogHit;
|
||
theFilterProc = DlgFilterUPP;
|
||
ignoreDefaultItem = true;
|
||
break;
|
||
#endif
|
||
}
|
||
theError = Nag (dialogID, initProc, hitProc, theFilterProc, ignoreDefaultItem, refcon);
|
||
if (!theError) {
|
||
(*nagUsage)->lastNag = currentTime;
|
||
saveMe = true;
|
||
}
|
||
}
|
||
if (!theError || saveMe) {
|
||
ChangedResource ((Handle) nagUsage);
|
||
if (!ResError ())
|
||
MyUpdateResFile (SettingsRefN);
|
||
}
|
||
ReleaseResource (nagUsage);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Nag
|
||
//
|
||
// Displays a Nag dialog to the user. The parameters look like so:
|
||
//
|
||
// dialogID - Resource ID of the 'DLOG' to be displayed
|
||
// initProc - NagDialogInitProcPtr callback issued after the dialog is successfully
|
||
// created. This allows you to perform any dialog special initialization
|
||
// before modeless dialog handling kicks in (for instance, so we can setup
|
||
// the feature list in the downgrade dialog).
|
||
// hitProc - NagDialogHitProcPtr that is called whenever an item in the dialog is hit.
|
||
// The dialog hit proc accepts a pointer to an event, a dialog pointer, and
|
||
// the item that was hit.
|
||
// theFilterProc - An optional modal filter proc which -- if present -- turns this nag into
|
||
// a movable modal dialog. Pass in nil if you want the Nag to behave in a
|
||
// modeless manner.
|
||
// ignoreDefaultItem - 'true' if you don't want item 1 to be a default item
|
||
//
|
||
// Quick nag dialog note... Whenever a new nag dialog is added to the resource fork
|
||
// don't forget:
|
||
//
|
||
// 1. Add the DLOG resID to the 'hlist' in AddDlgx
|
||
// 2. Add a 'dftb' resource with the same id to Common.r
|
||
//
|
||
// (There, it helps to write things down he said after spending an hour trying to figure
|
||
// out why GetDialogItemAsControl had broken and all of the dialog text was in the system font).
|
||
//
|
||
// Sort of, kind of important note.... Do not define a Nag with a dialog ID of zero!!
|
||
//
|
||
//stuvoid DebugDialogItems (DialogPtr theDialog);
|
||
|
||
OSErr Nag (short dialogID, NagDialogInitProcPtr initProc, NagDialogHitProcPtr hitProc, ModalFilterUPP theFilterProc, Boolean ignoreDefaultItem, long dialogRefcon)
|
||
|
||
{
|
||
MyWindowPtr dlogWin,
|
||
win;
|
||
DialogPtr dlog;
|
||
WindowPtr theWindow;
|
||
OSErr theError;
|
||
short dItem;
|
||
Boolean done;
|
||
|
||
if (!dialogID)
|
||
return (noErr);
|
||
|
||
// Is this nag already open?
|
||
if (win = FindNag (dialogID)) {
|
||
UserSelectWindow (GetMyWindowWindowPtr(win));
|
||
return (noErr);
|
||
}
|
||
|
||
dlogWin = GetNewMyDialog (dialogID, nil, nil, InFront);
|
||
theError = ResError ();
|
||
|
||
if (dlogWin) {
|
||
dlog = GetMyWindowDialogPtr (dlogWin);
|
||
theWindow = GetDialogWindow(dlog);
|
||
dlogWin->hit = hitProc;
|
||
dlogWin->hideme = true;
|
||
dlogWin->ignoreDefaultItem = ignoreDefaultItem;
|
||
dlogWin->centerAsDefault = true;
|
||
dlogWin->isNag = true;
|
||
dlogWin->dialogRefcon = dialogRefcon;
|
||
|
||
if (initProc)
|
||
theError = (*initProc) (dlog, dialogRefcon);
|
||
|
||
if (!theError) {
|
||
HiliteButtonOne (dlog);
|
||
theError = SubstituteLongStaticTextItems (dlog);
|
||
if (AppearanceVersion() < 0x0110)
|
||
; // Eventually we need to write our own AutoSizeDialog hack for Appearance 1.0.3
|
||
else {
|
||
AutoSizeDialog(dlog);
|
||
GrowGroupItems(theWindow,nil);
|
||
}
|
||
//DebugDialogItems (theDialog);
|
||
}
|
||
|
||
if (!theError) {
|
||
if (theFilterProc) {
|
||
StartMovableModal (dlog);
|
||
ShowWindow (theWindow);
|
||
AuditWindowOpen(dlogWin->windex,dialogID,0);
|
||
done = false;
|
||
while (!done) {
|
||
MovableModalDialog (dlog, theFilterProc, &dItem);
|
||
if (dItem == CANCEL_ITEM) // For some reason, CANCEL_ITEM is -1<> huh?
|
||
done = true;
|
||
else
|
||
if (hitProc)
|
||
done = (*hitProc) (nil, dlog, dItem, dialogRefcon);
|
||
}
|
||
EndMovableModal (dlog);
|
||
CloseMyWindow (theWindow);
|
||
AuditWindowClose(dlogWin->windex);
|
||
}
|
||
else
|
||
ShowMyWindow (theWindow);
|
||
}
|
||
else
|
||
DisposeDialog_ (dlog);
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
|
||
MyWindowPtr FindNag (short dialogID)
|
||
|
||
{
|
||
WindowPtr winWP;
|
||
MyWindowPtr win;
|
||
|
||
for (winWP = GetWindowList (); winWP; winWP = GetNextWindow (winWP)) {
|
||
win = GetWindowMyWindowPtr(winWP);
|
||
if (IsKnownWindowMyWindow(winWP) && win->dialogID == dialogID)
|
||
return (win);
|
||
}
|
||
return (nil);
|
||
}
|
||
|
||
|
||
|
||
|
||
void DownGradeDialog (void)
|
||
|
||
{
|
||
(void) Nag (NAG_DOWNGRADE_DLOG, DowngradeDlogInit, DowngradeDlogHit, nil, true, nil);
|
||
}
|
||
|
||
void NotifyDownGradeDialog (void)
|
||
|
||
{
|
||
FeatureCellHandle newFeatureList;
|
||
GrafPtr oldPort;
|
||
ControlHandle theControl;
|
||
ListHandle theList;
|
||
MyWindowPtr theDialogWin;
|
||
OSErr theError;
|
||
Rect contrlRect;
|
||
Size actualSize;
|
||
|
||
if (theDialogWin = FindNag (NAG_DOWNGRADE_DLOG)) {
|
||
DialogPtr theDialog = GetMyWindowDialogPtr (theDialogWin);
|
||
theError = GetDialogItemAsControl (theDialog, NAG_DOWNGRADE_FEATURE_LIST, &theControl);
|
||
if (!theError)
|
||
theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
|
||
if (!theError && theList)
|
||
if (newFeatureList = BuildFeatureList (false)) {
|
||
if (memcmp (LDRef (newFeatureList), LDRef((*theList)->userHandle), GetHandleSize (newFeatureList))) {
|
||
GetControlBounds(theControl,&contrlRect);
|
||
GetPort (&oldPort);
|
||
SetPort (GetWindowPort(GetDialogWindow(theDialog)));
|
||
InvalWindowRect(GetDialogWindow(theDialog),&contrlRect);
|
||
SetPort (oldPort);
|
||
}
|
||
UL (newFeatureList);
|
||
ZapHandle ((*theList)->userHandle);
|
||
(*theList)->userHandle = newFeatureList;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
Boolean RepayHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);
|
||
Boolean JunkDownHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);
|
||
extern Boolean PreRegHitProc (EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);
|
||
|
||
void RepayDialog (InitNagResultType pendingNagResult)
|
||
|
||
{
|
||
UserStateType newState;
|
||
short itemHit;
|
||
|
||
if (!Nag (REPAY_DLOG, nil, RepayHitProc, DlgFilterUPP, true, (long) &itemHit)) {
|
||
switch (itemHit) {
|
||
case REPAY_DITL_PAY_NOW:
|
||
if (pendingNagResult != gracelessRepayPending)
|
||
ComposeStdAlert (kAlertNoteAlert, GRACE_PERIOD_PAY_NOW_ALRT);
|
||
OpenAdwareURL (paidUser, REG_SITE, actionPay, paymentQuery, nil);
|
||
// If the user is trying to pay, we'll temporarily make them a 'repay' user. This effectively
|
||
// leaves them in paid mode for some length of time (currently an hour), or until they restart
|
||
// Eudora.
|
||
newState = repayUser;
|
||
break;
|
||
case REPAY_DITL_SPONSORED:
|
||
// Profile deadbeat users are subject to the same profile requirements
|
||
// as if they clicked 'Sponsored' in Payment & Registration
|
||
if (IsProfileDeadbeatUser()) {
|
||
// User must profile
|
||
if (SendUserToProfile())
|
||
if (kAlertStdAlertOKButton==ComposeStdAlert(Note,-PROFILING_NOW))
|
||
// user has indicated he has profiled. Make a playlist request
|
||
ForcePlaylistRequest();
|
||
return;
|
||
}
|
||
else
|
||
newState = IsRegisteredUser () ? regAdwareUser : adwareUser;
|
||
break;
|
||
}
|
||
}
|
||
// If no grace time is allowed, shoo them immediately into sponsored mode
|
||
if (pendingNagResult == gracelessRepayPending)
|
||
newState = IsRegisteredUser () ? regAdwareUser : adwareUser;
|
||
TransitionState (newState);
|
||
}
|
||
|
||
void JunkDownDialog (void)
|
||
{
|
||
short itemHit;
|
||
|
||
SetPrefBit(PREF_JUNK_MAILBOX,bJunkPrefDeadPluginsWarning);
|
||
if (!Nag (JUNKDOWN_DLOG, nil, JunkDownHitProc, DlgFilterUPP, true, (long) &itemHit)) {
|
||
switch (itemHit) {
|
||
case JUNKDOWN_DITL_PAY_NOW:
|
||
OpenAdwareURL (paidUser, REG_SITE, actionPay, paymentQuery, nil);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Boolean RepayHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)
|
||
{
|
||
short *dItem;
|
||
|
||
dItem = (short *) refcon;
|
||
*dItem = itemHit;
|
||
if (itemHit==REPAY_DITL_SHOW_VERSIONS)
|
||
{
|
||
OpenAdwareURL (paidUser, UPDATE_SITE, actionArchived, updateQuery, nil);
|
||
return false;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
Boolean JunkDownHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)
|
||
|
||
{
|
||
short *dItem;
|
||
|
||
dItem = (short *) refcon;
|
||
*dItem = itemHit;
|
||
if (itemHit==JUNKDOWN_DITL_MORE)
|
||
{
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, junkDownQuery, topicJunkDown);
|
||
return false;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
Boolean AdUserHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
|
||
{
|
||
short action;
|
||
|
||
switch (itemHit) {
|
||
case NAG_PLEASE_REGISTER_REGISTER_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
#ifdef I_HATE_THE_BOX
|
||
action = IsAdwareMode () ? actionRegisterAd : (IsFreeMode () ? actionRegisterFree : actionRegister50box);
|
||
#else
|
||
action = IsAdwareMode () ? actionRegisterAd : actionRegisterFree;
|
||
#endif
|
||
OpenAdwareURL (GetNagState (), REG_SITE, action, registrationQuery, nil);
|
||
break;
|
||
case NAG_PLEASE_REGISTER_LATER_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
Boolean IntroDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
{
|
||
// Transition to Adware no matter what
|
||
TransitionState (adwareUser);
|
||
switch (itemHit) {
|
||
case NAG_INTRO_DLOG_OK:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
break;
|
||
case NAG_INTRO_INFO_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionIntro, introQuery, nil);
|
||
break;
|
||
case NAG_INTRO_CODE_BTN:
|
||
CloseMyWindow(GetDialogWindow(theDialog));
|
||
CodeEntryDialog(nil);
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
|
||
// Vacuuous hit proc
|
||
Boolean HitMeHitMeHitMe (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
{
|
||
return (true);
|
||
}
|
||
|
||
|
||
|
||
OSErr DowngradeDlogInit (DialogPtr theDialog, long refcon)
|
||
|
||
{
|
||
ControlHandle theControl;
|
||
ListHandle theList;
|
||
OSErr theError;
|
||
Size actualSize;
|
||
|
||
theError = GetDialogItemAsControl (theDialog, NAG_DOWNGRADE_FEATURE_LIST, &theControl);
|
||
if (!theError)
|
||
theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
|
||
if (!theError)
|
||
if ((*theList)->userHandle = BuildFeatureList (false)) {
|
||
LSetDrawingMode (false, theList);
|
||
LAddRow (GetHandleSize ((*theList)->userHandle) / sizeof (FeatureCellRec), 0, theList);
|
||
LSetDrawingMode (true, theList);
|
||
}
|
||
|
||
return (theError);
|
||
}
|
||
|
||
|
||
FeatureCellHandle BuildFeatureList (Boolean ignoreUsage)
|
||
|
||
{
|
||
FeatureRecPtr featurePtr,
|
||
subFeaturePtr;
|
||
FeatureCellHandle featureCells;
|
||
FeatureCellPtr featureCellsPtr,
|
||
primaryCellPtr;
|
||
OSErr theError;
|
||
short numFeatures,
|
||
subCount,
|
||
featureCount;
|
||
FeatureID id;
|
||
|
||
featureCells = nil;
|
||
|
||
// Figure out how many feature we actually need to display in the list
|
||
numFeatures = GetHandleSize (gFeatureList) / sizeof (FeatureRec);
|
||
featureCount = 0;
|
||
featurePtr = LDRef (gFeatureList);
|
||
for (id = 0; id < numFeatures; ++id) {
|
||
if (IsFreeMode ()) {
|
||
if (featurePtr->resID != featNoResource && featurePtr->resID != featSimultaneousDirService)
|
||
++featureCount;
|
||
}
|
||
else
|
||
if (featurePtr->resID != featNoResource && HasFeature (id))
|
||
++featureCount;
|
||
++featurePtr;
|
||
}
|
||
|
||
if (featureCount) {
|
||
// Allocate space for the list data and store it in the useHandle (it will get released by the LDEF)
|
||
featureCells = NuHandleClear (featureCount * sizeof (FeatureCellRec) * 2);
|
||
theError = MemError ();
|
||
if (!theError) {
|
||
// Build the list data
|
||
featureCount = 0;
|
||
featurePtr = *gFeatureList;
|
||
featureCellsPtr = LDRef (featureCells);
|
||
for (id = 0; !theError && id < numFeatures; ++id) {
|
||
if ((IsFreeMode () && featurePtr->resID != featNoResource && featurePtr->resID != featSimultaneousDirService) ||
|
||
(!IsFreeMode () && featurePtr->resID != featNoResource && HasFeature (id))) {
|
||
// If the feature is NOT a sub feature, we'll add and process it
|
||
if (featurePtr->sub == '????') {
|
||
primaryCellPtr = featureCellsPtr;
|
||
theError = SetFeatureCell (&featureCellsPtr, featurePtr, ignoreUsage);
|
||
++featureCellsPtr;
|
||
++featureCount;
|
||
// Now, if this feature has sub features, look for them!
|
||
if (featurePtr->hasSubFeatures) {
|
||
subFeaturePtr = *gFeatureList;
|
||
subCount = numFeatures;
|
||
while (!theError && subCount--) {
|
||
if (subFeaturePtr->primary == featurePtr->primary && subFeaturePtr->sub != '????') {
|
||
if (featureCellsPtr->used = ((subFeaturePtr->flags & featurePresent) && subFeaturePtr->lastUsed && !ignoreUsage))
|
||
primaryCellPtr->used = true;
|
||
theError = SetFeatureCell (&featureCellsPtr, subFeaturePtr, ignoreUsage);
|
||
++featureCellsPtr;
|
||
++featureCount;
|
||
}
|
||
++subFeaturePtr;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
++featurePtr;
|
||
}
|
||
UL (featureCells);
|
||
if (!theError)
|
||
SetHandleBig (featureCells, featureCount * sizeof (FeatureCellRec) * 2);
|
||
}
|
||
}
|
||
UL (gFeatureList);
|
||
if (theError && featureCells)
|
||
ZapHandle (featureCells);
|
||
return (featureCells);
|
||
}
|
||
|
||
|
||
//
|
||
// SetFeatureCell
|
||
//
|
||
// Set a particular feature cell to reflect a given feature
|
||
//
|
||
|
||
OSErr SetFeatureCell (FeatureCellPtr *featureCellsPtr, FeatureRecPtr featurePtr, Boolean ignoreUsage)
|
||
|
||
{
|
||
FeatureResHandle resFeature;
|
||
char *nameAndDesc;
|
||
|
||
if (resFeature = GetResource (featureResourceType, (featurePtr)->resID)) {
|
||
nameAndDesc = (char *) &(*resFeature)->nameAndDesc;
|
||
// First, the name...
|
||
BlockMoveData (&nameAndDesc[0], (*featureCellsPtr)->description, nameAndDesc[0] + 1);
|
||
(*featureCellsPtr)->type = featurePtr->primary;
|
||
(*featureCellsPtr)->isName = true;
|
||
(*featureCellsPtr)->isSubFeature = (featurePtr->sub != '????');
|
||
(*featureCellsPtr)->used = ((featurePtr->flags & featurePresent) && featurePtr->lastUsed && !ignoreUsage);
|
||
|
||
// Next, the description...
|
||
++(*featureCellsPtr);
|
||
(*featureCellsPtr)->isSubFeature = (featurePtr->sub != '????');
|
||
BlockMoveData (&nameAndDesc[nameAndDesc[0] + 1], (*featureCellsPtr)->description, nameAndDesc[nameAndDesc[0] + 1] + 1);
|
||
ReleaseResource (resFeature);
|
||
}
|
||
return (ResError ());
|
||
}
|
||
|
||
Boolean DowngradeDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
|
||
{
|
||
switch (itemHit) {
|
||
case NAG_DOWNGRADE_YES_I_MEAN_IT_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
TransitionState (ValidRegCode (regFreeUser, nil, nil) ? regFreeUser : freeUser);
|
||
break;
|
||
case NAG_DOWNGRADE_CANCEL_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
OSErr FeaturesDlogInit (DialogPtr theDialog, long refcon)
|
||
{
|
||
return PutFeaturesInto(theDialog,NAG_FEATURES_FEATURE_LIST);
|
||
}
|
||
|
||
OSErr PutFeaturesInto (DialogPtr theDialog, short theItem)
|
||
{
|
||
ControlHandle theControl;
|
||
ListHandle theList;
|
||
OSErr theError;
|
||
Size actualSize;
|
||
|
||
theError = GetDialogItemAsControl (theDialog, theItem, &theControl);
|
||
if (!theError)
|
||
theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
|
||
if (!theError)
|
||
if ((*theList)->userHandle = BuildFeatureList (true)) {
|
||
LSetDrawingMode (false, theList);
|
||
LAddRow (GetHandleSize ((*theList)->userHandle) / sizeof (FeatureCellRec), 0, theList);
|
||
LSetDrawingMode (true, theList);
|
||
}
|
||
|
||
return (theError);
|
||
}
|
||
|
||
|
||
Boolean FeaturesDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
|
||
{
|
||
switch (itemHit) {
|
||
case NAG_FEATURES_YES_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
TransitionState (adwareUser);
|
||
break;
|
||
case NAG_FEATURES_CANCEL_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
|
||
OSErr NotGettingAdsDlogInit (DialogPtr dlog, AdFailurePtr adFailurePtr)
|
||
|
||
{
|
||
SetDIText (dlog, NAG_NOT_GETTING_ADS_ERROR_TEXT, adFailurePtr->text);
|
||
return (noErr);
|
||
}
|
||
|
||
|
||
Boolean NotGettingAdsDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
|
||
{
|
||
switch (itemHit) {
|
||
case NAG_NOT_GETTING_ADS_MORE_INFO_BTN:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicAdFailure);
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
|
||
Boolean NoAdsRevertToFreeDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
|
||
{
|
||
switch (itemHit) {
|
||
case NAG_NO_ADS_AT_ALL_OK_BTN:
|
||
TransitionState (deadbeatUser);
|
||
return (true);
|
||
break;
|
||
case NAG_NO_ADS_AT_ALL_MORE_INFO_BTN:
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicAdFailure);
|
||
return (false);
|
||
break;
|
||
}
|
||
return (true);
|
||
}
|
||
|
||
/*
|
||
void DebugDialogItems (DialogPtr theDialog)
|
||
|
||
{
|
||
ControlHandle theControl;
|
||
Str255 string;
|
||
Rect itemRect;
|
||
short count,
|
||
itemNo,
|
||
itemType;
|
||
ComposeString (string,"\p[%d, %d, %d, %d]", theDialog->portRect.left, theDialog->portRect.top, theDialog->portRect.right, theDialog->portRect.bottom);
|
||
DebugStr (string);
|
||
count = CountDITL (theDialog);
|
||
for (itemNo = 1; itemNo <= count; ++itemNo) {
|
||
GetDialogItemAsControl (theDialog, itemNo, &theControl);
|
||
// GetDialogItem (theDialog, itemNo, &itemType, (ControlHandle *) &theControl, &itemRect);
|
||
ComposeString (string,"\p[%d, %d, %d, %d]", (*theControl)->contrlRect.left, (*theControl)->contrlRect.top, (*theControl)->contrlRect.right, (*theControl)->contrlRect.bottom);
|
||
DebugStr (string);
|
||
}
|
||
}
|
||
*/
|
||
|
||
//
|
||
// SubstituteLongStaticTextItems
|
||
//
|
||
|
||
OSErr SubstituteLongStaticTextItems (DialogPtr theDialog)
|
||
|
||
{
|
||
ControlHandle theControl;
|
||
Str255 text;
|
||
Handle textHandle;
|
||
OSErr theError;
|
||
Rect itemRect;
|
||
long resID;
|
||
short count,
|
||
itemNo,
|
||
itemType;
|
||
|
||
theError = noErr;
|
||
|
||
count = CountDITL (theDialog);
|
||
for (itemNo = 1; !theError && itemNo <= count; ++itemNo) {
|
||
GetDialogItem (theDialog, itemNo, &itemType, (ControlHandle *) &theControl, &itemRect);
|
||
if (itemType & kStaticTextDialogItem) {
|
||
GetDialogItemText (theControl, text);
|
||
if (text[0] && text[1] == kTEXTResIDFollowsChar) {
|
||
text[1] = text[0] - 1;
|
||
StringToNum (&text[1], &resID);
|
||
if (textHandle = GetResource ('TEXT', resID)) {
|
||
theError = GetDialogItemAsControl (theDialog, itemNo, &theControl);
|
||
if (!theError)
|
||
theError = SetControlData (theControl, kControlEntireControl, kControlStaticTextTextTag, GetHandleSize (textHandle), LDRef (textHandle));
|
||
UL (textHandle);
|
||
ReleaseResource (textHandle);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
/************************************************************************
|
||
* GrowGroupItems - grow the group boxes in a window to contain their subcontrols
|
||
************************************************************************/
|
||
short GrowGroupItems(WindowPtr win,ControlHandle parentCntl)
|
||
{
|
||
ControlHandle cntl;
|
||
short maxV = 0, maxSubV;
|
||
short subI;
|
||
Rect rParent;
|
||
|
||
// Start off with the root control
|
||
if (!parentCntl)
|
||
if (GetRootControl(win,&parentCntl) || !parentCntl)
|
||
return 0; // no embedding
|
||
|
||
// First, figure out the size of the subcontrols
|
||
for (CountSubControls(parentCntl,&subI);subI;subI--)
|
||
if (!GetIndexedSubControl(parentCntl,subI,&cntl) && IsControlVisible(cntl))
|
||
{
|
||
maxSubV = GrowGroupItems(win,cntl);
|
||
maxV = MAX(maxV,maxSubV);
|
||
}
|
||
|
||
// Do we need to change anything?
|
||
GetControlBounds(parentCntl,&rParent);
|
||
if (maxV+MAX_APPEAR_RIM > rParent.bottom)
|
||
if (IsGroupControl(parentCntl))
|
||
{
|
||
SizeControl(parentCntl,ControlWi(parentCntl),maxV+MAX_APPEAR_RIM-rParent.top);
|
||
}
|
||
|
||
// Return final size
|
||
GetControlBounds(parentCntl,&rParent);
|
||
maxV = rParent.bottom;
|
||
|
||
return(maxV);
|
||
}
|
||
|
||
/************************************************************************
|
||
* IsGroupControl - is a control a group control?
|
||
************************************************************************/
|
||
Boolean IsGroupControl(ControlHandle cntl)
|
||
{
|
||
return GetControlReference(cntl)=='gpbx';
|
||
}
|
||
|
||
//
|
||
// GetIndNagID
|
||
//
|
||
// Grab an indexed nagID from Eudora itself.
|
||
//
|
||
|
||
OSErr GetIndNagID (short *nagID, short nagListID, short index)
|
||
|
||
{
|
||
NagListHandle nagList;
|
||
OSErr theError;
|
||
|
||
theError = resNotFound;
|
||
#ifdef I_HATE_THE_BOX
|
||
if (nagList = GetResource (NAG_LIST_TYPE, nagListID)) {
|
||
#else
|
||
if (nagList = GetResourceFromFile (NAG_LIST_TYPE, nagListID, AppResFile)) {
|
||
#endif
|
||
if (index > 0 && index <= (*nagList)->count) {
|
||
*nagID = (*nagList)->nagID[index - 1];
|
||
theError = noErr;
|
||
}
|
||
ReleaseResource (nagList);
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
//
|
||
// GetIndNagState
|
||
//
|
||
// Grab an indexed 'Nag ' resource from Eudora itself, depending on
|
||
// a particular user state.
|
||
//
|
||
|
||
OSErr GetIndNagState (NagPtr theNag, short *nagID, UserStateType state, short index)
|
||
|
||
{
|
||
NagHandle nag;
|
||
OSErr theError;
|
||
|
||
// First, get the indexed nag ID for the current state
|
||
theError = GetIndNagID (nagID, (short) state, index);
|
||
if (!theError) {
|
||
theError = resNotFound;
|
||
#ifdef I_HATE_THE_BOX
|
||
if (nag = GetResource (NAG_TYPE, *nagID)) {
|
||
#else
|
||
if (nag = GetResourceFromFile (NAG_TYPE, *nagID, AppResFile)) {
|
||
#endif
|
||
BlockMoveData (*nag, theNag, sizeof (NagRec));
|
||
theError = noErr;
|
||
}
|
||
ReleaseResource (nag);
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// TransitionState
|
||
//
|
||
|
||
void TransitionState (UserStateType newState)
|
||
|
||
{
|
||
WindowPtr winWP;
|
||
MyWindowPtr win;
|
||
UserStateType oldState;
|
||
short oldJunkCount = ETLCountTranslators(EMSF_JUNK_MAIL);
|
||
|
||
if (ValidUser (newState)) {
|
||
oldState = (*nagState)->state;
|
||
(*nagState)->state = newState;
|
||
if (!SettingsHandle (NAG_STATE_TYPE, nil, NAG_STATE_ID, nagState)) {
|
||
MyUpdateResFile (SettingsRefN);
|
||
DetachResource ((Handle) nagState);
|
||
}
|
||
|
||
CheckAdQT();
|
||
|
||
// Anything special we need to do when transitioning to a new state?
|
||
if (RepayUser (newState))
|
||
gTimeAtWhichRepayUsersBecomeSponsored = TickCount () + kDelayBeforeSwitchingRepayUsersToSponsored;
|
||
|
||
if ((FreeUser (oldState) && !FreeUser (newState)) ||
|
||
(!FreeUser (oldState) && FreeUser (newState))) {
|
||
// We need to defer making the state switch until we have torn down the
|
||
// current environment (in particular, closing windows have to know we
|
||
// are closing in Light as opposed to Sponsored mode). The state switch
|
||
// itself will occur in OpenNewSettings
|
||
(*nagState)->state = oldState;
|
||
RelaunchEudora (newState);
|
||
|
||
// Notify any plugins that care (Peanut) of our state change.
|
||
ETLEudoraModeNotification(EMS_ModeChanged, GetCurrentPayMode ());
|
||
}
|
||
else {
|
||
WindowPtr nextWP;
|
||
// 'OpenAdWindow' actually _does_ need to be called here regardless of
|
||
// mode so that it can perform various ad switching stuff itself.
|
||
// if (AdwareUser (newState))
|
||
OpenAdWindow ();
|
||
winWP = GetWindowList ();
|
||
while (winWP) {
|
||
nextWP = GetNextWindow (winWP); // Just in case we close 'winWP' (like we do w/the ad window)
|
||
win = GetWindowMyWindowPtr (winWP);
|
||
if (IsKnownWindowMyWindow (winWP) && win->transition)
|
||
(*win->transition) (win, oldState, newState);
|
||
winWP = nextWP;
|
||
}
|
||
// Notify any plugins that care (Peanut, SpamWatch suite) of our state change.
|
||
ETLEudoraModeNotification(EMS_ModeChanged, GetCurrentPayMode ());
|
||
}
|
||
|
||
// plug-ins may have become active
|
||
if (JunkPrefNeedIntro()) JunkIntro();
|
||
|
||
// on the other hand, they may not...
|
||
if (oldJunkCount>ETLCountTranslators(EMSF_JUNK_MAIL)) JunkDownDialog();
|
||
}
|
||
gCanPayMode = UserHasValidPaidModeRegcode();
|
||
}
|
||
|
||
/************************************************************************
|
||
* UserHasValidPaidModeRegcode - does the user have a valid paid-mode code?
|
||
************************************************************************/
|
||
Boolean UserHasValidPaidModeRegcode(void)
|
||
{
|
||
int policy, month;
|
||
|
||
return ValidRegCode(paidUser,&policy,&month) && PolicyCheck(policy,month);
|
||
}
|
||
|
||
//
|
||
// DaysSinceNagBase
|
||
//
|
||
// Returns the number of days since the nag base for the specified nag dialog.
|
||
// If the nag base is zero (indicating that we've never been nagged),
|
||
//
|
||
|
||
uLong DaysSinceNagBase (short dialogID)
|
||
|
||
{
|
||
NagUsageHandle nagUsage;
|
||
uLong daysSince;
|
||
|
||
daysSince = 0;
|
||
nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, dialogID, SettingsRefN);
|
||
if (nagUsage)
|
||
daysSince = (SecondsToWholeDay (LocalDateTime ()) - SecondsToWholeDay ((*nagUsage)->nagBase)) / kSecondsPerDay;
|
||
return (daysSince);
|
||
}
|
||
|
||
|
||
uLong DurationOfSchedule (UserStateType state, short whichSchedule)
|
||
|
||
{
|
||
NagRec nag;
|
||
uLong duration;
|
||
short lastScheduledDay,
|
||
nagID;
|
||
|
||
duration = 0;
|
||
if (!GetIndNagState (&nag, &nagID, state, whichSchedule))
|
||
if ((lastScheduledDay = MostSignificantSetBit (nag.schedule)) >= 0)
|
||
duration = lastScheduledDay + 1;
|
||
return (duration);
|
||
}
|
||
|
||
|
||
//
|
||
// SecondsToWholeDay
|
||
//
|
||
// Takes a time expressed in seconds since 1/1/04 and returns the
|
||
// time expressed as midnight on that day since 1/1/04 (if that
|
||
// makes any sense whatsoever...). The purpose of this routine is
|
||
// normalize internal times to more easily perform date calculation
|
||
// in absolute day increments without rounding problems. For example,
|
||
// the number of days between 11:59 PM on 3/3/99, and 12:00 AM on
|
||
// 3/4/99 would be 1. This is much easier to accomplish across day,
|
||
// month and year boundaries if we're always dealing with days timed
|
||
// at midnight.
|
||
//
|
||
|
||
uLong SecondsToWholeDay (uLong secs)
|
||
|
||
{
|
||
DateTimeRec dateTime;
|
||
|
||
SecondsToDate (secs, &dateTime);
|
||
dateTime.hour = 0;
|
||
dateTime.minute = 0;
|
||
dateTime.second = 0;
|
||
DateToSeconds (&dateTime, &secs);
|
||
return (secs);
|
||
}
|
||
|
||
|
||
//
|
||
// MostSignificantSetBit
|
||
//
|
||
// Find the most significant set bit of an unsigned 32 bit value
|
||
// using an inefficient algorithm but oh well you've been warned
|
||
// and you're not going to call this 10 million times in a tight
|
||
// loop everytime through the main event loop anyway, right?
|
||
//
|
||
// Returns -1 if no bits are set.
|
||
//
|
||
|
||
short MostSignificantSetBit (UInt32 value)
|
||
|
||
{
|
||
short bit;
|
||
|
||
bit = 31;
|
||
while (value)
|
||
if (value & 0x80000000)
|
||
return (bit);
|
||
else {
|
||
--bit;
|
||
value <<= 1;
|
||
}
|
||
return (-1);
|
||
}
|
||
|
||
|
||
|
||
|
||
void RelaunchEudora (UserStateType newState)
|
||
|
||
{
|
||
OpenNewSettings (&SettingsFileSpec, true, newState);
|
||
}
|
||
|
||
|
||
//
|
||
// UpdateCheck
|
||
//
|
||
// Contact eudora.com and check for application updates.
|
||
// If there are updates write the results into a file
|
||
// Open a window displaying this information.
|
||
//
|
||
// silently - Indicates that this check was not initiated by the user
|
||
//
|
||
|
||
OSErr UpdateCheck (Boolean silently, Boolean archives)
|
||
|
||
{
|
||
FSSpec updateSpec;
|
||
Handle url;
|
||
OSErr theError;
|
||
long reference;
|
||
|
||
theError = noErr;
|
||
|
||
// First, we need a file -- but make sure this is a temporary file because the server might be
|
||
// handing us an empty update to signify "no change"
|
||
NewTempSpec (Root.vRef, Root.dirId, nil, &updateSpec);
|
||
|
||
// (old way...)
|
||
// theError = FSMakeFSSpec (Root.vRef, Root.dirId, GetRString (filename, UPDATE_FILE), &updateSpec);
|
||
// if (theError == fnfErr)
|
||
// theError = FSpCreate (&updateSpec, CREATOR, 'TEXT', smSystemScript);
|
||
|
||
// Send a GET to the server to request updates
|
||
if (url = GenerateAdwareURL (GetNagState (), UPDATE_SITE, archives ? actionArchived : actionUpdate, updateQuery, nil)) {
|
||
theError = DownloadURL (LDRef (url), &updateSpec, silently, FinishedUpdateCheck, &reference, nil);
|
||
ZapHandle (url);
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
|
||
//
|
||
// FinishedUpdateCheck
|
||
//
|
||
// Completion routine for DownloadURL. If 'silently' is true, the update check has been
|
||
// performed without a user action. If no error's were found we need to check to see if
|
||
// the checksum on the file has changed. If so, display the window. If not, do
|
||
// nothing.
|
||
//
|
||
|
||
void FinishedUpdateCheck (long silently, OSErr theError, DownloadInfo *info)
|
||
|
||
{
|
||
MyWindowPtr win;
|
||
NagUsageHandle nagUsage;
|
||
FSSpec updateSpec;
|
||
Str255 filename;
|
||
uLong hash;
|
||
long fileSize;
|
||
short oldResFile;
|
||
Boolean differentChecksum;
|
||
|
||
differentChecksum = false;
|
||
if (!silently)
|
||
TellPayWindowTheUpdateCheckIsDone (theError);
|
||
|
||
// Now that the update is complete, check to see if the server gave us anything.
|
||
// If the update file is empty, no updates are available and we should not
|
||
// automatically display the update page
|
||
fileSize = theError ? 0 : FSpDFSize (&info->spec);
|
||
|
||
// Make the real update file spec
|
||
if (!theError) {
|
||
theError = FSMakeFSSpec (Root.vRef, Root.dirId, GetRString (filename, UPDATE_FILE), &updateSpec);
|
||
if (theError == fnfErr)
|
||
theError = FSpCreate (&updateSpec, CREATOR, 'TEXT', smSystemScript);
|
||
}
|
||
|
||
// Exchange it with the new one -- but only if this file is not empty!
|
||
if (!theError && fileSize) {
|
||
theError = FSpExchangeFiles (&updateSpec, &info->spec);
|
||
if (!theError)
|
||
FSpDelete (&info->spec);
|
||
}
|
||
|
||
// Compare the checksum to what we've snuck in the update check's nag aux field but only if this is
|
||
// NOT the first time we've performed an update check. If the checksum has changed (or if this is
|
||
// not a silent check) we'll display the update information to the user. We also have to save the
|
||
// checksum back to the settings file in the update nag's aux field.
|
||
if (!theError && fileSize) {
|
||
hash = HashFile (&updateSpec);
|
||
oldResFile = CurResFile ();
|
||
if (nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, UPDATE_CHECK, SettingsRefN)) {
|
||
differentChecksum = (*nagUsage)->aux && ((*nagUsage)->aux != hash);
|
||
if (!(*nagUsage)->aux || differentChecksum) {
|
||
(*nagUsage)->aux = hash;
|
||
ChangedResource ((Handle) nagUsage);
|
||
if (!ResError ())
|
||
MyUpdateResFile (SettingsRefN);
|
||
}
|
||
ReleaseResource (nagUsage);
|
||
}
|
||
UseResFile (oldResFile);
|
||
}
|
||
|
||
if (!theError && (!silently || differentChecksum))
|
||
win = OpenText (&updateSpec, nil, nil, nil, true, nil, true, true);
|
||
}
|
||
|
||
#define KRHashPrime (2147483629)
|
||
|
||
uLong HashFile (FSSpec *spec)
|
||
|
||
{
|
||
Handle text;
|
||
Ptr textPtr;
|
||
Size len;
|
||
int Bit;
|
||
uLong sum;
|
||
|
||
sum = 0;
|
||
if (!Snarf (spec, &text, 0)) {
|
||
len = GetHandleSize (text);
|
||
textPtr = *text;
|
||
while (len--) {
|
||
for (Bit = 0x80; Bit != 0; Bit >>= 1) {
|
||
sum += sum;
|
||
if (sum >= KRHashPrime) sum -= KRHashPrime;
|
||
if ((*textPtr) & Bit) ++sum;
|
||
if (sum >= KRHashPrime) sum -= KRHashPrime;
|
||
}
|
||
++textPtr;
|
||
}
|
||
}
|
||
return (sum + 1);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* CheckAdQT - if we're in adware mode, make sure QuickTime is installed
|
||
************************************************************************/
|
||
void CheckAdQT(void)
|
||
{
|
||
if (IsAdwareMode() && !HaveQuickTime(0x0300))
|
||
{
|
||
// No QuickTime. Must revert to freeware.
|
||
switch (ComposeStdAlert(kAlertNoteAlert,CANT_AD)) {
|
||
case 1:
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicNoQuickTime);
|
||
EjectBuckaroo = true;
|
||
break;
|
||
case 2:
|
||
TransitionState ((*nagState)->state==regAdwareUser ? regFreeUser : freeUser);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// AdFailureCheck
|
||
//
|
||
// Nag the user if they refuse to get ads (whether they want to or not).
|
||
// We overload the nagBaseTime filed of the nagUsageRec to indicate the
|
||
// last time we found that we checked for ad failure.
|
||
//
|
||
// Note that we only look at the state of the 'NoAdsAuxRec' when we enter
|
||
// into a new day. At this point, the contents of the data structure
|
||
// reflect the ad fetching state for the entirety of the previous day
|
||
// which means we don't have to worry too much about mid day transitions.
|
||
//
|
||
// If we did not get ads on the previous day we checked for ads...
|
||
// ...bump the deadbeat counter
|
||
// ...check to see if we're due for a nag
|
||
//
|
||
// If we _did_ get ads...
|
||
// ...bump the consecutive day counter
|
||
// ...check to see if we should adjust the deadbeat counter and
|
||
// set the consecutive day counter back to zero
|
||
//
|
||
// Regardless, at the beginning of a new day we have to initialize 'adsAreFailing'
|
||
//
|
||
|
||
OSErr AdFailureCheck (NagUsageHandle nagUsage, uLong currentTime, Boolean *nagMe, short *dialogID, PStr errString)
|
||
|
||
{
|
||
OSErr theError;
|
||
|
||
theError = noErr;
|
||
*nagMe = false;
|
||
*errString = 0;
|
||
|
||
// Are we checking on a new day? If so, what happened yesterday?
|
||
if (SecondsToWholeDay ((*nagUsage)->nagBase) != SecondsToWholeDay (currentTime)) {
|
||
if (NoAdsRec.adsAreFailing) {
|
||
if (NoAdsRec.deadbeatCounter >= kDaysUntilNoAdsNag) {
|
||
*nagMe = true;
|
||
if (NoAdsRec.deadbeatCounter >= kDeadbeatDays) {
|
||
*dialogID = NAG_FRIGGING_HACKER_DLOG;
|
||
NoAdsRec.deadbeatCounter = kDeadbeatDays-1; // in case we ever get back to sponsored mode
|
||
}
|
||
else
|
||
*dialogID = NAG_NOT_GETTING_ADS_DLOG;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Keep track of the last time we checked for ad failures
|
||
(*nagUsage)->nagBase = currentTime;
|
||
|
||
return (theError);
|
||
}
|
||
|
||
#endif
|
||
|
||
|
||
#ifdef DEBUG
|
||
void DebugNagging (void)
|
||
|
||
{
|
||
NagDialogInitProcPtr initProc;
|
||
NagDialogHitProcPtr hitProc;
|
||
ModalFilterUPP theFilterProc;
|
||
AdFailureRec adFailure;
|
||
UserStateType oldState;
|
||
MyWindowPtr theDialogWin;
|
||
DialogPtr theDialog;
|
||
Str255 string;
|
||
Str255 sErr;
|
||
short dItem;
|
||
long dialogID;
|
||
Boolean ignoreDefaultItem;
|
||
|
||
if (theDialogWin = GetNewMyDialog (NAG_DEBUG, nil, nil, InFront)) {
|
||
theDialog = GetMyWindowDialogPtr (theDialogWin);
|
||
oldState = GetNagState ();
|
||
SetDItemState (theDialog, 8, oldState == newUser);
|
||
SetDItemState (theDialog, 9, oldState == paidUser);
|
||
SetDItemState (theDialog, 10, oldState == freeUser);
|
||
SetDItemState (theDialog, 11, oldState == adwareUser);
|
||
SetDItemState (theDialog, 12, oldState == regFreeUser);
|
||
SetDItemState (theDialog, 13, oldState == regAdwareUser);
|
||
SetDItemState (theDialog, 14, oldState == deadbeatUser);
|
||
StartMovableModal (theDialog);
|
||
ShowWindow (GetDialogWindow(theDialog));
|
||
do {
|
||
MovableModalDialog (theDialog, DlgFilterUPP, &dItem);
|
||
if (dItem >= 6 && dItem <= 14) {
|
||
SetDItemState (theDialog, 6, dItem == 6);
|
||
SetDItemState (theDialog, 7, dItem == 7);
|
||
SetDItemState (theDialog, 8, dItem == 8);
|
||
SetDItemState (theDialog, 9, dItem == 9);
|
||
SetDItemState (theDialog, 10, dItem == 10);
|
||
SetDItemState (theDialog, 11, dItem == 11);
|
||
SetDItemState (theDialog, 12, dItem == 12);
|
||
SetDItemState (theDialog, 13, dItem == 13);
|
||
SetDItemState (theDialog, 14, dItem == 14);
|
||
}
|
||
} while (dItem != ok && dItem != cancel);
|
||
EndMovableModal (theDialog);
|
||
if (dItem == ok) {
|
||
GetDIText (theDialog, 4, string);
|
||
dItem = ok;
|
||
}
|
||
DisposeDialog_ (theDialog);
|
||
if (dItem == ok) {
|
||
if (string[0]) {
|
||
StringToNum (string, &dialogID);
|
||
if (dialogID) {
|
||
initProc = nil;
|
||
hitProc = nil;
|
||
theFilterProc = nil;
|
||
ignoreDefaultItem = false;
|
||
adFailure.text[0] = 0;
|
||
adFailure.error = noErr;
|
||
switch (dialogID) {
|
||
case NAG_INTRO_DLOG:
|
||
hitProc = IntroDlogHit;
|
||
theFilterProc = DlgFilterUPP;
|
||
break;
|
||
case JUNKDOWN_DLOG:
|
||
hitProc = JunkDownHitProc;
|
||
theFilterProc = DlgFilterUPP;
|
||
break;
|
||
case REPAY_DLOG:
|
||
hitProc = RepayHitProc;
|
||
theFilterProc = DlgFilterUPP;
|
||
break;
|
||
case NAG_PLEASE_REGISTER_DLOG:
|
||
hitProc = AdUserHit;
|
||
break;
|
||
case NAG_DOWNGRADE_DLOG:
|
||
initProc = DowngradeDlogInit;
|
||
hitProc = DowngradeDlogHit;
|
||
break;
|
||
case NAG_FEATURES_DLOG:
|
||
initProc = FeaturesDlogInit;
|
||
hitProc = FeaturesDlogHit;
|
||
break;
|
||
case NAG_NOT_GETTING_ADS_DLOG:
|
||
initProc = NotGettingAdsDlogInit;
|
||
hitProc = NotGettingAdsDlogHit;
|
||
adFailure.error = 503;
|
||
GetRString(sErr, HTTP_ERR_STRN + 3);
|
||
ComposeRString (adFailure.text,HTTP_ERR_FORMAT,sErr,adFailure.error);
|
||
break;
|
||
case NAG_FRIGGING_HACKER_DLOG:
|
||
hitProc = NoAdsRevertToFreeDlogHit;
|
||
theFilterProc = DlgFilterUPP;
|
||
ignoreDefaultItem = true;
|
||
break;
|
||
}
|
||
Nag (dialogID, initProc, hitProc, theFilterProc, ignoreDefaultItem, (long) &adFailure.text);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
void AutoSwitchClientMode(void)
|
||
{
|
||
if (NewClientModePlusOne)
|
||
{
|
||
switch (NewClientModePlusOne-1)
|
||
{
|
||
case 0: TransitionState(adwareUser); break;
|
||
case 1:
|
||
case 3:
|
||
// User is being beaten dead
|
||
if (kAlertStdAlertOtherButton==ComposeStdAlert(Note,PROFILE_FAILURE))
|
||
{
|
||
if (SendUserToProfile())
|
||
if (kAlertStdAlertOKButton==ComposeStdAlert(Note,-PROFILING_NOW))
|
||
{
|
||
// user has indicated he has profiled. Make a playlist request
|
||
// if he has profiled, it won't switch him to light after all
|
||
// if he has failed to profile, it will switch him to light again
|
||
ForcePlaylistRequest();
|
||
break;
|
||
}
|
||
}
|
||
// Hasta la vista, bebe's kids
|
||
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
|
||
TransitionState(NewClientModePlusOne-1==3 ? profileDeadbeatUser : deadbeatUser);
|
||
#endif
|
||
break;
|
||
case 2: TransitionState(paidUser); break;
|
||
}
|
||
NewClientModePlusOne = 0;
|
||
}
|
||
}
|
||
|
||
extern ModalFilterUPP DlgFilterUPP;
|
||
Boolean PlaylistNagHitProc (EventRecord *event, DialogPtr theDialog, short itemHit,long refcon);
|
||
OSErr PlaylistNagInitProc(DialogPtr theDialog,long refcon);
|
||
/************************************************************************
|
||
* DoPlaylistNag - Ask the user if it's ok to send the audit stats
|
||
************************************************************************/
|
||
Boolean DoPlaylistNag(short nagID)
|
||
{
|
||
Boolean retVal = userCanceledErr != Nag(nagID,PlaylistNagInitProc,PlaylistNagHitProc,nil,false, nagID);
|
||
ZapResource(NAG_REQUEST_TYPE,nagID);
|
||
return retVal;
|
||
}
|
||
|
||
#define kPlNagOK 2
|
||
#define kPlNagProfile 1
|
||
#define kPlNagExplain 3
|
||
#define kPlNagParam 5
|
||
#define kPlNagFeatures 8
|
||
/************************************************************************
|
||
* PlaylistNagHitProc - handle item hits
|
||
************************************************************************/
|
||
Boolean PlaylistNagHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)
|
||
{
|
||
switch (itemHit) {
|
||
case kPlNagExplain:
|
||
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionProfileidReqd, profileidReqdQuery , nil);
|
||
CloseMyWindow(GetDialogWindow(theDialog));
|
||
return true;
|
||
case kPlNagProfile:
|
||
SendUserToProfile();
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
return true;
|
||
break;
|
||
default:
|
||
CloseMyWindow (GetDialogWindow(theDialog));
|
||
return true;
|
||
break;
|
||
}
|
||
return (false);
|
||
}
|
||
|
||
/************************************************************************
|
||
* PlaylistNagInitProc - init dialog by filling in the fields
|
||
************************************************************************/
|
||
OSErr PlaylistNagInitProc(DialogPtr theDialog,long refcon)
|
||
{
|
||
Handle text = GetResource(NAG_REQUEST_TYPE,refcon);
|
||
Str255 s;
|
||
|
||
if (text)
|
||
{
|
||
MakePStr(s,*text,GetHandleSize(text));
|
||
SetDIText(theDialog,kPlNagParam,s);
|
||
}
|
||
|
||
// the feature list
|
||
if (refcon==PLNAG_LEVEL1_DLOG)
|
||
PutFeaturesInto(theDialog,kPlNagFeatures);
|
||
|
||
return noErr;
|
||
}
|