Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
OTStreamLogViewer.c
/* |
File: StreamLogWatcher.c |
Contains: A program to display information logged to the STREAMS log module. |
Written by: Quinn "The Eskimo!" |
Copyright: © 1998-2000 by Apple Computer, Inc., all rights reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
*/ |
#define qDebug 1 |
///////////////////////////////////////////////////////////////////// |
// Standard C stuff. |
#include <stdio.h> |
// Pick up standard OT APIs. |
#include <OpenTransport.h> |
// Pick up standard toolbox APIs. |
#include <QuickDraw.h> |
#include <Fonts.h> |
#include <Windows.h> |
#include <Menus.h> |
#include <TextEdit.h> |
#include <Dialogs.h> |
#include <Memory.h> |
#include <Resources.h> |
#include <TextUtils.h> |
#include <Scrap.h> |
#include <Devices.h> |
#include <CodeFragments.h> |
#include <Gestalt.h> |
#include <Processes.h> |
#include <InternetConfig.h> |
#include <Lists.h> |
// MoreIsBetter modules |
#include "MoreAppleEvents.h" |
#include "MoreMemory.h" |
#include "MoreQuickDraw.h" |
#include "MoreAppearance.h" |
#include "MoreWindows.h" |
#include "MoreDialogs.h" |
#include "MoreMenus.h" |
#include "MoreLists.h" |
#include "MoreErrors.h" |
// Pick up our resource definitions. |
#include "StreamLogResources.h" |
// Pick up our memory management module. |
#include "OTMemoryReserve.h" |
// Pick up the file logging stuff. |
#include "FileLogging.h" |
// Pick up the log engine prototypes. |
#include "LogEngine.h" |
///////////////////////////////////////////////////////////////////// |
// OTDebugStr is defined in a wacky OT library. Let's just use |
// lowercase debugstr instead. |
#define OTDebugStr debugstr |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Applications Globals ***** |
enum { |
kCreator = 'SlVw' |
}; |
static Boolean gQuitNow; |
// Set to true to drop out of the main event loop. |
static DialogPtr gMainWindow = nil; |
// The main window. Pretty much the only window at |
// the moment. |
static ListHandle gLogList = nil; |
// The List Manager list in the main window. |
static ListDefUPP gListDefProcUPP; |
// A UPP for the list definition proc we use |
// for this list. We have a dummy LDEF whose |
// resource ID we supply to LNew. That LDEF just |
// calls through the list's refCon (assuming it isn't |
// nil). We poke this UPP into that refCon, and hence |
// the LDEF calls us. This allows for source |
// level debugging of the LDEF without messing around |
// with self-modifying code. |
static UserItemUPP gListUserItemUpdateUPP; |
// A UPP for the user item in gMainWindow that contains |
// the gLogList. This routine simply calls LUpdate |
// (oh, and also draws the frame for the list). |
static Rect gMainWindowGrowBounds; |
// Bounds which we pass to GrowWindow to constrain how |
// much the window can grow or shrink. The topLeft |
// co-ordinate in the minimum size (which we get from |
// the window's portRect just after we create it) |
// and the botRight are derived from screenBits.bounds |
// (which Window Manager special cases to allow windows |
// to grow as large as possible on the screen(s)). |
static Boolean gScrollWhileLogging = true; |
// This value is a mirror of the state of the menu |
// item. We mirror it to make it quicker to fetch while |
// we're logging. |
static Boolean gStartOnLaunch = false; |
// This value is a mirror of the state of the menu |
// item. |
static SInt16 gModuleID; // -1 => log all modules |
static SInt16 gStreamID; // -1 => log all streams |
// These are the logging filters. This only take effect |
// if the All Traces radio button is /not/ on. The values |
// are set by means of a modal dialog. |
static void DirtyPreferences(void); |
// forward |
static void PreferencesIdle(void); |
// forward |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Apple Event Handlers ***** |
static pascal OSErr AEOpenApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused(reply) |
#pragma unused(handlerRefcon) |
OSStatus err; |
err = MoreAEGotRequiredParams(theAppleEvent); |
return err; |
} |
static pascal OSErr AEOpenDocumentsHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused(reply) |
#pragma unused(handlerRefcon) |
OSStatus err; |
err = MoreAEGotRequiredParams(theAppleEvent); |
return err; |
} |
static pascal OSErr AEQuitApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused(reply) |
#pragma unused(handlerRefcon) |
OSStatus err; |
err = MoreAEGotRequiredParams(theAppleEvent); |
if (err == noErr) { |
gQuitNow = true; |
} |
return err; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** List Logging Stuff ***** |
enum { |
// kMaxListRows is the maximum number of rows we can have in our |
// scrolling list. List Manager imposes an overall limit of 32K. |
// It also imposes a 32K limit on the amount of data in the list. |
// We use 4 bytes per row of data, which would imply this limit |
// should be 8K. 5000 is just to keep it within reasonable bounds. |
kMaxListRows = 5000, |
// When the list exceeds the above limit, we delete some rows from |
// the front of the list. We do this in chunks for efficiency's |
// sake. This is the size of the chunk we delete. |
kNumberOfRowsToDelete = 100 |
}; |
static LogEntryPtr ListGetLogEntryForRow(SInt16 rowNumber) |
// Given a row number (zero based), this routine returns |
// the log entry for that row. if there is no log entry |
// (which can happen if the routine is called by the LDEF |
// as the row is added), it returns nil. |
{ |
Cell thisCell; |
LogEntryPtr result; |
SInt16 dataSize; |
thisCell.h = 0; |
thisCell.v = rowNumber; |
dataSize = sizeof(result); |
LGetCell(&result, &dataSize, thisCell, gLogList); |
if ( dataSize != sizeof(result) ) { |
result = nil; |
} |
return result; |
} |
static void ListSetLogEntryForRow(SInt16 rowNumber, LogEntryPtr rowEntry) |
// This routine sets the data for the given rowNumber (zero based) |
// to rowEntry, to later be retrieved by ListGetLogEntryForRow. |
{ |
Cell thisCell; |
thisCell.h = 0; |
thisCell.v = rowNumber; |
LSetCellWhite(&rowEntry, sizeof(rowEntry), thisCell, gLogList); |
} |
static void FlagsToShortString(char flags, Str255 flagStr) |
// This routine sets flagStr to an 8 character string |
// that represents flags in a very compact form, suitable |
// for drawing by the LDEF. |
{ |
OTMemset(&flagStr[1], '-', 8); |
flagStr[0] = 8; |
if ((flags & SL_TRACE) != 0) { |
flagStr[1] = 'T'; |
} |
if ((flags & SL_ERROR) != 0) { |
flagStr[2] = 'E'; |
} |
if ((flags & SL_CONSOLE) != 0) { |
flagStr[3] = 'C'; |
} |
// flagStr[4] is blank |
if ((flags & SL_FATAL) != 0) { |
flagStr[5] = 'F'; |
} |
if ((flags & SL_NOTIFY) != 0) { |
flagStr[6] = 'N'; |
} |
if ((flags & SL_WARN) != 0) { |
flagStr[7] = 'W'; |
} |
if ((flags & SL_NOTE) != 0) { |
flagStr[8] = 'N'; |
} |
} |
// These constants define the start (and hence the width) |
// of each of the fields in our LDEF. |
enum { |
kFieldStart0 = 4, |
kFieldStart1 = kFieldStart0 + 40, |
kFieldStart2 = kFieldStart1 + 60, |
kFieldStart3 = kFieldStart2 + 80, |
kFieldStart4 = kFieldStart3 + 80, |
kFieldStart5 = kFieldStart4 + 40, |
kFieldStart6 = kFieldStart5 + 40, |
kFieldStart7 = kFieldStart6 + 0 |
}; |
static void ListDefProcDraw(Rect *theCellRect, SInt16 rowNumber) |
// This routine is called by the LDEF in response to an |
// lDraw message. The basic idea is to get the log entry |
// for the given row and then use the given rectangle to |
// render the data in the entry. |
{ |
LogEntryPtr thisEntry; |
Str255 tmpStr; |
UInt32 strLen; |
thisEntry = ListGetLogEntryForRow(rowNumber); |
OTAssert("ListDefProc: No data for cell", thisEntry != nil); |
if (thisEntry != nil) { |
NumToString(thisEntry->fLogHeader.seq_no, tmpStr); |
MoveTo(theCellRect->left + kFieldStart0, theCellRect->bottom - 4); |
DrawString(tmpStr); |
MoveTo(theCellRect->left + kFieldStart1, theCellRect->bottom - 4); |
FlagsToShortString(thisEntry->fLogHeader.flags, tmpStr); |
DrawString(tmpStr); |
MoveTo(theCellRect->left + kFieldStart2, theCellRect->bottom - 4); |
DateString(thisEntry->fLogHeader.ttime, shortDate, tmpStr, nil); |
DrawString(tmpStr); |
TimeString(thisEntry->fLogHeader.ttime, true, tmpStr, nil); |
MoveTo(theCellRect->left + kFieldStart3, theCellRect->bottom - 4); |
DrawString(tmpStr); |
NumToString(thisEntry->fLogHeader.mid, tmpStr); |
MoveTo(theCellRect->left + kFieldStart4, theCellRect->bottom - 4); |
DrawString(tmpStr); |
NumToString(thisEntry->fLogHeader.sid, tmpStr); |
MoveTo(theCellRect->left + kFieldStart5, theCellRect->bottom - 4); |
DrawString(tmpStr); |
// We clip strLen at 255 to avoid attempting to draw more than |
// a 255 character string (which won't work 'cause it's a Pascal |
// string). We could have used DrawText, but 255 characters is |
// enough for me. If you need more, look at the file log. |
strLen = thisEntry->fTextLength; |
if ( strLen > 255 ) { |
strLen = 255; |
} |
BlockMoveData((char *) thisEntry + sizeof(LogEntry), &tmpStr[1], strLen); |
tmpStr[0] = strLen; |
MoveTo(theCellRect->left + kFieldStart6, theCellRect->bottom - 4); |
DrawString(tmpStr); |
} |
} |
static pascal void ListDefProc(SInt16 message, Boolean drawSelected, |
Rect *theCellRect, Cell theCell, |
SInt16 dataOffset, SInt16 dataLen, |
ListRef listH) |
// Our list definition proc (LDEF). This routine is called |
// back by the List Manager when it wants to draw (or highlight) |
// a cell in the list in our main window. The implementation |
// basically dispatches based on message. Of cours, there's only |
// one basic action, so this is just a glorified wrapper for |
// ListDefProcDraw. |
{ |
#pragma unused(dataOffset) |
#pragma unused(dataLen) |
#if qDebug |
OTAssert("ListDefProc: This LDEF only works for gLogList", listH == gLogList); |
#else |
#pragma unused(listH) |
#endif |
switch (message) { |
case lInitMsg: |
// do nothing |
break; |
case lDrawMsg: |
case lHiliteMsg: |
EraseRect(theCellRect); |
ListDefProcDraw(theCellRect, theCell.v); |
if (drawSelected) { |
MoreSetMagicHiliteMode(); |
InvertRect(theCellRect); |
} |
break; |
case lCloseMsg: |
// do nothing |
break; |
default: |
OTDebugStr("ListDefProc: Unrecognised message"); |
break; |
} |
} |
static pascal void ListUserItemUpdateProc(WindowPtr dlg, short item) |
// A Dialog Manager user item update proc. This routine |
// is called by the Dialog Manager when it wants to draw |
// the list user item. We respond by drawing the list frame |
// and then calling through to List Manager to draw the list itself. |
{ |
OSStatus err; |
OSStatus junk; |
Rect itemRect; |
MoreThemeDrawingState drawState; |
// This erase is critical, because List Manager is not smart |
// enough to erase the area below the defined cells when |
// the defined cells don't take up enough space to occupy the |
// entire viewable area. |
err = MoreGetThemeDrawingState(&drawState); |
if (err == noErr) { |
junk = MoreNormalizeThemeDrawingState(); |
MoreAssert(junk == noErr); |
GetDialogItemRect(dlg, item, &itemRect); |
EraseRect(&itemRect); |
err = MoreSetThemeDrawingState(drawState, true); |
} |
MoreAssertQ(junk == noErr); |
// Draw the frame. |
InsetRect(&itemRect, -1, -1); |
FrameRect(&itemRect); |
// Call List Manager to draw the list. |
LUpdateWhite(dlg->clipRgn, gLogList); |
} |
static void RecordLogEntryToList(LogEntryPtr thisEntry) |
// This routine is called at idle time when new list entries |
// are noticed. The basic idea is to append the new entries |
// as rows at the end of the list. We also have to deal with |
// keeping the list size within bounds and scrolling the new |
// items into view if the user asked to. |
{ |
SInt16 newRow; |
SInt32 i; |
LogEntryPtr anEntry; |
Cell tmpCell; |
OTAssert("RecordLogEntryToList: gLogList is nil", gLogList != nil); |
// First check to see whether we have too many rows in the list. If so, |
// delete the first kNumberOfRowsToDelete rows, making sure we release |
// the data associated with deleted rows. |
if ( ((**gLogList).dataBounds.bottom - (**gLogList).dataBounds.top) > kMaxListRows) { |
for (i = 0; i < kNumberOfRowsToDelete; i++) { |
anEntry = ListGetLogEntryForRow(i); |
OTAssert("RecordLogEntryToList: No list data", anEntry != nil); |
ReleaseLogEntry(anEntry); |
} |
LDelRowWhite(kNumberOfRowsToDelete, 0, gLogList); |
} |
// Now add the row, and set the data for the new row. |
// Make sure we call RetainLogEntry to denote the extra |
// reference to this data. |
LSetDrawingMode(false, gLogList); |
newRow = LAddRowWhite(1, 32767, gLogList); |
RetainLogEntry(thisEntry); |
LSetDrawingMode(true, gLogList); |
ListSetLogEntryForRow(newRow, thisEntry); |
tmpCell.h = 0; |
tmpCell.v = newRow; |
// Finally, scroll the list to show the new item if the user |
// asked us to. |
if ( gScrollWhileLogging ) { |
LScrollWhite(0, newRow, gLogList); |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** General User Interface Stuff ***** |
static void SizeMainWindow(SInt16 newWidth, SInt16 newHeight) |
// This routine is used to resize the main window, |
// making sure that the list in the window is also resized |
// to match. |
{ |
Rect itemRect; |
// Round the height to the closest 16 pixel boundary. |
// We need to make sure the list stays a multiple of |
// 16 pixels high (otherwise List Manager behaves |
// suckily), and we do this by making sure the window |
// is always a multiple of 16 pixels high. |
newHeight = (newHeight + 8) / 16 * 16 - 1; |
// Resize the window. |
SizeWindow(gMainWindow, newWidth, newHeight, true); |
// Now sync up the dialog item and the list view rect |
// with the new window size. This took some time to get |
// right. |
GetDialogItemRect(gMainWindow, ditLogEntryList, &itemRect); |
itemRect.right = gMainWindow->portRect.right; |
itemRect.bottom = gMainWindow->portRect.bottom; |
SetDialogItemRect(gMainWindow, ditLogEntryList, &itemRect); |
itemRect.right -= 15; |
itemRect.bottom -= 15; |
LSizeWhite(itemRect.right - itemRect.left, |
itemRect.bottom - itemRect.top, |
gLogList); |
} |
static void SetMainWindowVisibility(Boolean isVisible) |
// We use this routine to show and hide the main |
// window, making sure that all the UI elements that |
// depend on this state are kept in sync. |
{ |
Str255 menuItemText; |
OTAssert("SetMainWindowVisibility: No list handle", gLogList != nil); |
if (isVisible) { |
ShowWindow(gMainWindow); |
if ( ! MoreTitleBarOnScreen(gMainWindow) ) { |
MoveWindow(gMainWindow, 100, 100, false); |
} |
GetIndString(menuItemText, rMiscStrings, strHideLogWindow); |
} else { |
HideWindow(gMainWindow); |
GetIndString(menuItemText, rMiscStrings, strShowLogWindow); |
} |
SetMenuItemText( GetMenuHandle(mFile), iShowHideLogWindow, menuItemText); |
} |
static void SetScrollWhileLogging(Boolean scrollWhileLogging) |
// We use this routine to set the scroll while logging state, |
// keeping the UI in sync. |
{ |
gScrollWhileLogging = scrollWhileLogging; |
CheckItem( GetMenuHandle(mFile), iScrollWhileLogging, gScrollWhileLogging); |
} |
static void SetStartOnLaunch(Boolean startOnLaunch) |
// We use this routine to set the scroll while logging state, |
// keeping the UI in sync. |
{ |
gStartOnLaunch = startOnLaunch; |
CheckItem( GetMenuHandle(mFile), iStartOnLaunch, gStartOnLaunch); |
} |
static void DisplayError(OSStatus errNum) |
{ |
Str255 becauseString; |
Str255 errNumStr; |
if (errNum != noErr && errNum != userCanceledErr) { |
NumToString(errNum, errNumStr); |
MoreLookupError(rErrorTable, errNum, becauseString); |
ParamText(becauseString, errNumStr, "\p", "\p"); |
(void) StopAlert(rErrorAlert, GetOKAlertFilterUPP()); |
} |
} |
static void UpdateStartStopUI(void) |
// This routine is used to sync up the enabled state of |
// the start and stop user interface to the current |
// logging state. |
{ |
Str255 tmpStr; |
SetDialogControlEnable(gMainWindow, ditStop, LoggingActive()); |
SetDialogControlEnable(gMainWindow, ditStart, ! LoggingActive()); |
if ( LoggingActive() ) { |
GetIndString(tmpStr, rMiscStrings, strStopLogging); |
} else { |
GetIndString(tmpStr, rMiscStrings, strStopLogging); |
} |
SetMenuItemText(GetMenuHandle(mFile), iStartStopLogging, tmpStr); |
} |
static void UIStartLogging(void) |
// This routine handles the user clicking on the Start |
// logging button (or the equivalent menu command). It |
// gathers the logging parameters from the dialog items |
// and then calls the logging back end. |
{ |
OSStatus err; |
UInt32 traceInfoCount; |
struct trace_ids *traceInfo; |
struct trace_ids traceInfoBuffer; |
// Gather the logging parameters from the dialog. |
traceInfoCount = 0; |
traceInfo = nil; |
if ( GetDialogControlBoolean(gMainWindow, ditLogTraces) ) { |
traceInfoCount = 1; |
traceInfo = &traceInfoBuffer; |
if ( GetDialogControlBoolean(gMainWindow, ditAllTraces) ) { |
traceInfoBuffer.ti_mid = -1; |
traceInfoBuffer.ti_sid = -1; |
} else { |
traceInfoBuffer.ti_mid = gModuleID; |
traceInfoBuffer.ti_sid = gStreamID; |
} |
if ( GetDialogControlValue(gMainWindow, ditTraceLevel) == iAll ) { |
traceInfoBuffer.ti_level = -1; |
} else { |
traceInfoBuffer.ti_level = GetDialogControlValue(gMainWindow, ditTraceLevel) - iLevelOffset; |
} |
} |
// If we're supposed to be logging to a file, start the |
// file logging module. |
err = noErr; |
if ( GetDialogControlBoolean(gMainWindow, ditLogToFile) ) { |
err = StartFileLogging(); |
} |
// Start the back end with the parameters we've already |
// worked out. |
if (err == noErr) { |
err = StartLogging(GetDialogControlBoolean(gMainWindow, ditLogErrors), traceInfoCount, traceInfo); |
} |
// Update the user interface to reflect the change |
// of logging state. |
if ( err == noErr ) { |
UpdateStartStopUI(); |
} |
// Clean up. |
if (err != noErr) { |
if ( FileLoggingActive() ) { |
StopFileLogging(); |
} |
} |
DisplayError(err); |
} |
static void DoIdle(void); |
// forward |
static void UIStopLogging(void) |
// This routine is called in response to the user |
// clicking on the Stop logging button (or the equivalent |
// menu command). It basically calls through to the back |
// end to stop the logging process, then flushouts out |
// any remaining log entries to the log list and file, |
// then shuts down file logging if it was started up. |
{ |
StopLogging(); |
// Calling DoIdle will flush out any log entries that remain |
// in the LIFO, which is important to do before we close the |
// file. |
DoIdle(); |
if ( FileLoggingActive() ) { |
StopFileLogging(); |
} |
UpdateStartStopUI(); |
} |
static Boolean UIConfirmAndStop(void) |
// A generic routine which we call whenever we have to |
// perform some action that will change a logging parameter. |
// We don't want the current logging parameters to be out |
// of sync with the user interface, and I'm too lazy to |
// support adjusting the parameters dynamically, so instead |
// we stop logging whenever the user changes a logging parameter. |
// This routine puts up an alert asking whether that's OK, |
// and then stops logging if it is. It returns true if |
// it's OK to perform a change in logging parameters (either |
// because logging was off, or because we just turned it off). |
{ |
Boolean result; |
result = false; |
if ( LoggingActive() ) { |
if ( CautionAlert(rConfirmStopAlert, GetOKCancelAlertFilterUPP()) == kStdOkItemIndex ) { |
UIStopLogging(); |
result = true; |
} |
} else { |
result = true; |
} |
return result; |
} |
static void UIPoseFilterDialog(void) |
// This routine puts up the dialog that lets the user |
// enter trace filtering parameters. It populates the dialog |
// based on gModuleID and gStreamID, and if the user clicks |
// OK it writes back to those globals. |
{ |
DialogPtr dlg; |
Str255 tmpStr; |
SInt32 tmpLong; |
SInt16 hitItem; |
// Create and prepare the dialog. |
dlg = GetNewDialog(rFilterDialog, nil, (WindowPtr) -1); |
if (dlg != nil) { |
SetupStandardDialogItems(dlg, kStdOkItemIndex, kStdCancelItemIndex); |
SetDialogControlBoolean(dlg, ditAllModules, gModuleID == -1); |
SetDialogControlBoolean(dlg, ditChosenModules, gModuleID != -1); |
if (gModuleID != -1) { |
NumToString(gModuleID, tmpStr); |
SetDialogItemString(dlg, ditModuleID, tmpStr); |
} |
SetDialogControlBoolean(dlg, ditAllStreams, gStreamID == -1); |
SetDialogControlBoolean(dlg, ditChosenStreams, gStreamID != -1); |
if (gStreamID != -1) { |
NumToString(gStreamID, tmpStr); |
SetDialogItemString(dlg, ditStreamID, tmpStr); |
} |
SelectDialogItemText(dlg, ditModuleID, 0, 32767); |
// Run the dialog and respond to user events. |
ShowWindow(dlg); |
do { |
ModalDialog(GetOKCancelModalFilterUPP(), &hitItem); |
switch (hitItem) { |
case ditAllModules: |
case ditChosenModules: |
ToggleDialogControlBoolean(dlg, ditAllModules); |
ToggleDialogControlBoolean(dlg, ditChosenModules); |
break; |
case ditAllStreams: |
case ditChosenStreams: |
ToggleDialogControlBoolean(dlg, ditAllStreams); |
ToggleDialogControlBoolean(dlg, ditChosenStreams); |
break; |
case kStdOkItemIndex: |
case kStdCancelItemIndex: |
// do nothing |
break; |
default: |
OTDebugStr("UIPoseFilterDialog: Weird hitItem"); |
} |
} while ( hitItem != kStdOkItemIndex && hitItem != kStdCancelItemIndex ); |
// If OK, gather the results of the dialog and write |
// it back to the global variables. |
if (hitItem == kStdOkItemIndex) { |
if ( GetDialogControlBoolean(dlg, ditAllModules) ) { |
gModuleID = -1; |
} else { |
GetDialogItemString(dlg, ditModuleID, tmpStr); |
StringToNum(tmpStr, &tmpLong); |
gModuleID = tmpLong; |
} |
if ( GetDialogControlBoolean(dlg, ditAllStreams) ) { |
gStreamID = -1; |
} else { |
GetDialogItemString(dlg, ditStreamID, tmpStr); |
StringToNum(tmpStr, &tmpLong); |
gStreamID = tmpLong; |
} |
DirtyPreferences(); |
} |
DisposeDialog(dlg); |
} else { |
DisplayError(memFullErr); |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Log Entry Processing ***** |
// gLoggedCount is used to record the total number of entries |
// we have logged. We use this number to maintain a static text |
// item in the dialog. |
static UInt32 gLoggedCount = 0; |
// gLastLoggedCount and gLastDroppedCount record the last value |
// of these two variables we used to fill out the static text fields. |
// We keep a record of this so that, each time around the idle loop, |
// we don't go to the trouble of updating the static text items unless |
// they have changed. |
static UInt32 gLastLoggedCount = 0; |
static UInt32 gLastDroppedCount = 0; |
static pascal void RecordLogEntry(LogEntryPtr thisEntry, void *refCon) |
// This routine is a callback, called by the logging module (which |
// we in turn called at idle time), when a new log entry has been |
// found. We call our two logging subsystems (on screen list |
// and file) to log the entry to the appropriate places. The |
// routines we call are responsible for retaining thisEntry |
// if they need it to hang around; otherwise it will be disposed |
// when this routine returns. |
{ |
#pragma unused(refCon) |
RecordLogEntryToList(thisEntry); |
RecordLogEntryToFile(thisEntry); |
gLoggedCount += 1; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Menu Handling ***** |
static void AdjustMenus(void) |
// This routine adjusts the menus based on the state of the |
// program in preparation for a call to MenuKey or MenuSelect. |
{ |
MenuHandle menuH; |
Boolean logAtFront; |
Boolean logHasSelection; |
// File |
menuH = GetMenuHandle(mFile); |
MoreSetMenuItemEnable(menuH, iClose, FrontWindow() == gMainWindow ); |
// Edit |
menuH = GetMenuHandle(mEdit); |
logAtFront = (FrontWindow() == gMainWindow); |
logHasSelection = (LSelectedLine(gLogList) != -1); |
DisableItem(menuH, iUndo); |
MoreSetMenuItemEnable(menuH, iCut, logAtFront && logHasSelection); |
MoreSetMenuItemEnable(menuH, iCopy, logAtFront && logHasSelection); |
DisableItem(menuH, iPaste); |
MoreSetMenuItemEnable(menuH, iClear, logAtFront && logHasSelection); |
MoreSetMenuItemEnable(menuH, iSelectAll, logAtFront && ! LIsEmpty(gLogList) ); |
} |
static void CopySelectedListEntriesToClip(void) |
// This routine is used to implement the Copy and Cut |
// menu commands. It iterates through the list of selected |
// cells and adds the text from each of them into a handle. |
// It then puts that handle into the system scrap. |
{ |
OSStatus err; |
Cell listCell; |
Handle clipTextH; |
LogEntryPtr anEntry; |
char *entryAsText; |
// Create a handle for the text we're copying. |
clipTextH = NewHandle(0); |
err = MoreMemError(clipTextH); |
if (err == noErr) { |
// Iterate through the selected cells, adding their |
// text representations to the handle. |
listCell.v = 0; |
listCell.h = 0; |
while ( LGetSelect(true, &listCell, gLogList) ) { |
anEntry = ListGetLogEntryForRow(listCell.v); |
OTAssert("ClearSelectedListEntries: No list data", anEntry != nil); |
entryAsText = LogEntryToCString(anEntry); |
err = PtrAndHand(entryAsText, clipTextH, OTStrLength(entryAsText)); |
if (err != noErr) { |
break; |
} |
listCell.v += + 1; |
listCell.h = 0; |
} |
} |
// Put the newly created text into the system scrap. |
if (err == noErr) { |
(void) ZeroScrap(); |
err = PutScrap( GetHandleSize(clipTextH), 'TEXT', *clipTextH ); |
if (err > 0) { |
err = 0; |
} |
} |
// Clean up. |
if ( clipTextH != nil ) { |
DisposeHandle(clipTextH); |
} |
DisplayError(err); |
} |
static void ClearSelectedListEntries(void) |
// This routine is used to implement the Cut and Clear |
// menu commands. It iterates through the list of selected |
// cells deleting them. |
{ |
Cell listCell; |
LogEntryPtr anEntry; |
// I turn off drawing mode, delete all the selected cells, turn |
// drawing mode back on and then invalidate the entire list. This |
// causes flashing if there's only one cell selected, but it works |
// much better in the case where there are *lots* of cells selected. |
// I chose to do it this way because I think the latter case will be |
// more prevelant, and I don't have time to handle both cases properly. |
LSetDrawingMode(false, gLogList); |
listCell.v = 0; |
listCell.h = 0; |
while ( LGetSelect(true, &listCell, gLogList) ) { |
anEntry = ListGetLogEntryForRow(listCell.v); |
OTAssert("ClearSelectedListEntries: No list data", anEntry != nil); |
ReleaseLogEntry(anEntry); |
LDelRowWhite(1, listCell.v, gLogList); |
// Don't increment listCell.v because we've just deleted it. |
// LGetSelect will continue looking from the current co-ordinates, |
// which will be the next selected cell. |
// listCell.v += + 1; |
listCell.h = 0; |
} |
LSetDrawingMode(true, gLogList); |
InvalDialogItem(gMainWindow, ditLogEntryList); |
} |
static void DoMenu(SInt32 menuAndItem) |
// Handle a menu command. menuAndItme is a longint |
// with the menu number in the top 16 bits and the |
// menu item in the bottom 16, ie the format returned |
// by MenuSelect and MenuKey. |
// |
// Yeah, this should be more than one procedure (-: |
{ |
SInt16 menu; |
SInt16 item; |
UInt32 startTicks; |
UInt32 junkLong; |
Str255 daName; |
SInt16 junk; |
VersRecHndl versH; |
SInt8 s; |
startTicks = TickCount(); |
menu = menuAndItem >> 16; |
item = menuAndItem & 0x0FFFF; |
switch (menu) { |
case mApple: |
switch (item) { |
case iAbout: |
versH = (VersRecHndl) Get1Resource('vers', 1); |
OTAssert("", versH != nil); |
s = HGetState( (Handle) versH ); |
HLock( (Handle) versH ); |
ParamText( (**versH).shortVersion, "\p", "\p", "\p"); |
junk = Alert(rAboutBox, GetOKAlertFilterUPP()); |
HSetState( (Handle) versH, s ); |
break; |
default: |
GetMenuItemText(GetMenuHandle(mApple), item, daName); |
junk = OpenDeskAcc(daName); |
break; |
} |
break; |
case mFile: |
switch (item) { |
case iClose: |
SetMainWindowVisibility(false); |
DirtyPreferences(); |
break; |
case iShowHideLogWindow: |
SetMainWindowVisibility( ! ((WindowPeek) gMainWindow)->visible ); |
DirtyPreferences(); |
break; |
case iStartStopLogging: |
if ( LoggingActive() ) { |
FlashDialogControl(gMainWindow, ditStop); |
UIStopLogging(); |
} else { |
FlashDialogControl(gMainWindow, ditStart); |
UIStartLogging(); |
} |
break; |
case iScrollWhileLogging: |
SetScrollWhileLogging( ! gScrollWhileLogging); |
DirtyPreferences(); |
break; |
case iStartOnLaunch: |
SetStartOnLaunch( ! gStartOnLaunch); |
DirtyPreferences(); |
break; |
case iQuit: |
gQuitNow = true; |
break; |
default: |
OTDebugStr("DoMenu: Weird File menu item"); |
break; |
} |
break; |
case mEdit: |
OTAssert("DoMenu: Edit menu should be disabled if gMainWindow not at front", gMainWindow == FrontWindow()); |
switch (item) { |
case iCut: |
CopySelectedListEntriesToClip(); |
ClearSelectedListEntries(); |
break; |
case iCopy: |
CopySelectedListEntriesToClip(); |
break; |
case iClear: |
ClearSelectedListEntries(); |
break; |
case iSelectAll: |
LSelectAll(gLogList); |
break; |
default: |
OTDebugStr("DoMenu: Weird Edit menu item"); |
break; |
} |
default: |
// OTDebugStr("DoMenu: Weird menu item"); |
break; |
} |
// Unhighlight the menu, delaying to avoid a quick flash. |
if ( ! gQuitNow ) { |
while ( TickCount() < startTicks + 6 ) { |
Delay(1, &junkLong); |
} |
HiliteMenu(0); |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Low Level Event Handling ***** |
static void DoMouseDown(EventRecord *event) |
// Handle a mouse down event. This is standard |
// Mac OS stuff. Call FindWindow to determine what |
// type and event it was (and what window it happened in) |
// and then handle each case. |
{ |
SInt16 partCode; |
WindowPtr hitWindow; |
long growResult; |
partCode = FindWindow(event->where, &hitWindow); |
switch ( partCode ) { |
case inGoAway: |
if ( TrackGoAway(hitWindow, event->where) ) { |
if ( hitWindow == gMainWindow ) { |
SetMainWindowVisibility(false); |
DirtyPreferences(); |
} else { |
OTDebugStr("DoMouseDown: Does not handle closing other windows"); |
} |
} |
break; |
case inMenuBar: |
AdjustMenus(); |
DoMenu(MenuSelect(event->where)); |
break; |
case inDrag: |
DragWindow(hitWindow, event->where, &qd.screenBits.bounds); |
DirtyPreferences(); |
break; |
case inGrow: |
growResult = GrowWindow(hitWindow, event->where, &gMainWindowGrowBounds); |
if ( growResult != 0 ) { |
Rect tmpRect; |
if ( hitWindow == gMainWindow ) { |
SizeMainWindow(growResult, growResult >> 16); |
DirtyPreferences(); |
} else { |
SizeWindow(hitWindow, growResult, growResult >> 16, true); |
} |
tmpRect = hitWindow->portRect; |
tmpRect.top = tmpRect.bottom - 15; |
InvalRect(&tmpRect); |
tmpRect = hitWindow->portRect; |
tmpRect.left = tmpRect.right - 15; |
InvalRect(&tmpRect); |
} |
break; |
case inZoomIn: |
case inZoomOut: |
if ( TrackBox(hitWindow, event->where, partCode) ) { |
ZoomWindow(hitWindow, partCode, false); |
if (hitWindow == gMainWindow) { |
SizeMainWindow(hitWindow->portRect.right - hitWindow->portRect.left, |
hitWindow->portRect.bottom - hitWindow->portRect.top); |
} |
// Force everything to redraw. I know this isn't perfect, |
// but I'd rather have window zooming working but flashy |
// than not have it. |
EraseRect(&hitWindow->portRect); |
InvalRect(&hitWindow->portRect); |
} |
break; |
default: |
// do nothing |
break; |
} |
} |
static void DoIdle(void) |
// Called each time through the main event loop. This routine |
// handles getting the new log entries out of the log module |
// and into RecordLogEntry, which sends them to the file and list |
// logging code. It also idles the file and preferences modules, |
// which delay actions for a time to prevent unnecessary disk thrashing. |
{ |
Str255 tmpStr; |
UInt32 droppedCount; |
// Don't ask me why, or I'll start to whimper. |
OTIdle(); |
// OK, I'll explain. Basically there's a weird concurrency |
// hole in OT such that, if you don't have any other network |
// activity, strlog messages will never get delivered |
// to the client. I've reported this as a bug [Radar ID 2218220], |
// but it's easy to work around (calling OTIdle will deliver |
// the messages) and it's so deep in the OT kernel that |
// I'm not sure anyone will ever want to take the risk |
// associated with fixing it. |
ForEachNewLogEntry(RecordLogEntry, nil); |
FileLoggingIdle(); |
PreferencesIdle(); |
// Update the static text to reflect the number |
// of logs we've seen and/or dropped. |
if ( gLoggedCount != gLastLoggedCount ) { |
NumToString(gLoggedCount, tmpStr); |
SetDialogItemString(gMainWindow, ditLoggedCount, tmpStr); |
gLastLoggedCount = gLoggedCount; |
} |
droppedCount = NumberOfDroppedLogEntries(); |
if ( droppedCount != gLastDroppedCount ) { |
NumToString(droppedCount, tmpStr); |
SetDialogItemString(gMainWindow, ditDroppedCount, tmpStr); |
gLastDroppedCount = droppedCount; |
} |
} |
static pascal void GetLogListCellText(ListHandle listH, const Cell *listCell, Str255 cellText) |
// This routine is a callback from LDoKey to get the text of |
// a cell. We return the sequence number for the log entry. |
// This allows LDoKey to implement "select by typing". |
{ |
#pragma unused(listH) |
LogEntryPtr thisEntry; |
cellText[0] = 0; |
thisEntry = ListGetLogEntryForRow(listCell->v); |
OTAssert("GetLogListCellText: No data for cell", thisEntry != nil); |
if (thisEntry != nil) { |
NumToString(thisEntry->fLogHeader.seq_no, cellText); |
} |
} |
static void DoUpdateEvent(EventRecord *event) |
// In general we handle update events through |
// the Dialog Manager DialogSelect routine. However, |
// for the main window, we have to manually draw the |
// grow box. This is not as easy as it might seem, because |
// we don't want the silly fake scroll bars in our window. |
// We we set the clip region to the bottom right 15 x 15 |
// rectangle, so we get the grow box and nothing else. |
// |
// Of course, Mac OS 8 Appearance does silly things here |
// too, because the grow box is now part of the window |
// structure region. But it recognises the call to DrawGrowIcon |
// and does The Right Thing (tm). |
{ |
RgnHandle tmpRgn; |
Rect growRect; |
if ( (WindowPtr) event->message == gMainWindow ) { |
tmpRgn = NewRgn(); |
OTAssert("MainEventLoop: Could not create temporary region", tmpRgn != nil); |
CopyRgn(gMainWindow->clipRgn, tmpRgn); |
growRect = gMainWindow->portRect; |
growRect.top = growRect.bottom - 15; |
growRect.left = growRect.right - 15; |
ClipRect(&growRect); |
DrawGrowIcon( (WindowPtr) event->message ); |
SetClip(tmpRgn); |
DisposeRgn(tmpRgn); |
} |
} |
static void DoMainWindowHit(EventRecord *event, SInt16 hitItem, SInt16 oldTraceLevel) |
// Handle a hit on an item in the main dialog window. |
// A big switch state with a bunch of UI twiddling |
// that, if we had a real windowing toolbox, would not |
// be necessary! |
// |
// oldTraceLevel is the old value of the trace level popup |
// menu. The MainEventLoop saves this value before calling |
// DialogSelect. DialogSelect will change the popup and |
// tells us that it's done so. We then ask the user to |
// confirm whether they want to stop logging. If they say |
// they don't, we reset the popup back to the old value. |
{ |
Point localWhere; |
switch ( hitItem ) { |
case ditStart: |
UIStartLogging(); |
break; |
case ditStop: |
UIStopLogging(); |
break; |
case ditLogErrors: |
case ditLogTraces: |
case ditLogToFile: |
if ( UIConfirmAndStop() ) { |
ToggleDialogControlBoolean(gMainWindow, hitItem); |
DirtyPreferences(); |
} |
break; |
case ditAllTraces: |
case ditTracesMatching: |
if ( UIConfirmAndStop() ) { |
SetDialogControlBoolean(gMainWindow, ditAllTraces, hitItem == ditAllTraces); |
SetDialogControlBoolean(gMainWindow, ditTracesMatching, hitItem == ditTracesMatching); |
SetDialogControlEnable(gMainWindow, ditSetupFilter, hitItem == ditTracesMatching); |
DirtyPreferences(); |
} |
break; |
case ditSetupFilter: |
if ( UIConfirmAndStop() ) { |
UIPoseFilterDialog(); |
} |
break; |
case ditTraceLevel: |
if ( UIConfirmAndStop() ) { |
DirtyPreferences(); |
} else { |
SetDialogControlValue(gMainWindow, ditTraceLevel, oldTraceLevel); |
} |
break; |
case ditLogEntryList: |
localWhere = event->where; |
GlobalToLocal(&localWhere); |
(void) LClickWhite(localWhere, event->modifiers, gLogList); |
break; |
default: |
OTDebugStr("DoMainWindowHit: Unexpected hitItem"); |
break; |
} |
} |
static void MainEventLoop(void) |
{ |
EventRecord event; |
WindowRef hitWindow; |
SInt16 hitItem; |
SInt16 oldTraceLevel; |
if (gStartOnLaunch) { |
// I tried flashing the button but it looks kinda ugly. |
// |
// FlashDialogControl(gMainWindow, ditStart); |
UIStartLogging(); |
} |
gQuitNow = false; |
do { |
DoIdle(); |
(void) WaitNextEvent(everyEvent, &event, 60, nil); |
SetPort(gMainWindow); |
if ( IsDialogEvent(&event) ) { |
oldTraceLevel = GetDialogControlValue(gMainWindow, ditTraceLevel); |
if ( DialogSelect(&event, &hitWindow, &hitItem) ) { |
if (hitWindow == gMainWindow) { |
DoMainWindowHit(&event, hitItem, oldTraceLevel); |
} else { |
OTDebugStr("MainEventLoop: What other dialogs?"); |
} |
} |
} |
switch (event.what) { |
case keyDown: |
case autoKey: |
if ( event.what == keyDown && (event.modifiers & cmdKey) != 0 ) { |
AdjustMenus(); |
DoMenu(MenuKey(event.message & charCodeMask)); |
} else if ( FrontWindow() == gMainWindow) { |
UInt8 typedChar; |
typedChar = (event.message & charCodeMask); |
if ( typedChar == kClearCharCode || |
typedChar == kBackspaceCharCode || |
typedChar == kDeleteCharCode ) { |
ClearSelectedListEntries(); |
} else { |
LDoKey(gLogList, &event, GetLogListCellText); |
} |
} |
break; |
case mouseDown: |
DoMouseDown(&event); |
break; |
case updateEvt: |
DoUpdateEvent(&event); |
break; |
case activateEvt: |
if ( (WindowPtr) event.message == gMainWindow ) { |
LActivateWhite( (event.modifiers & 1) != 0, gLogList); |
} |
break; |
case kHighLevelEvent: |
(void) AEProcessAppleEvent(&event); |
break; |
default: |
// do nothing |
break; |
} |
} while ( ! gQuitNow ); |
// As we're quitting, we'd better shut down the logging |
// systems. |
if ( LoggingActive() ) { |
FlashDialogControl(gMainWindow, ditStop); |
UIStopLogging(); |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Prefs Management ***** |
enum { |
kPrefsMagic = kCreator |
}; |
// PrefsRecord is stored in Internet Config (if it is |
// installed) because it's a handy preferences system |
// and I really don't want to create yet another file |
// in the Preferences folder. Because it's stored |
// on the disk, it has to be 68K aligned (becasue it might |
// be written by a 68K and read by a PPC, or vice versa). |
// |
// The fields in the record pretty much mirror the state |
// in the user interface elements. We save all this state |
// so the user (namely me during testing!) doesn't have to |
// reset it each time they launch the program. |
#pragma options align=mac68k |
struct PrefsRecord0 { |
OSType magic; // kPrefsMagic |
Rect mainWindowPosition; |
SInt16 moduleID; |
SInt16 streamID; |
Boolean logToFile; |
Boolean logErrors; |
Boolean logTraces; |
Boolean tracesMatching; |
Boolean mainWindowVisible; |
Boolean scrollWhileLogging; |
}; |
typedef struct PrefsRecord0 PrefsRecord0, *PrefsRecord0Ptr; |
struct PrefsRecord { |
OSType magic; // kPrefsMagic |
Rect mainWindowPosition; |
SInt16 moduleID; |
SInt16 streamID; |
Boolean logToFile; |
Boolean logErrors; |
Boolean logTraces; |
Boolean tracesMatching; |
Boolean mainWindowVisible; |
Boolean scrollWhileLogging; |
Boolean startOnLaunch; |
}; |
typedef struct PrefsRecord PrefsRecord, *PrefsRecordPtr; |
#pragma options align=reset |
static UInt8 *gICPrefsKey = "\p536C5677¥PrefsRecord"; |
// The Internet Config key we use to save the PrefsRecord. |
static ICInstance gICInstance = nil; |
// Our connection to Internet Config. Note that |
// gICInstance != nil implies that it's safe to call IC. |
enum { |
kPrefChangeWriteDelay = 600, |
// The length of time between when the preferences |
// are dirtied and when we write them. This allows |
// multiple preference writes to be collated into one, |
// thereby saving some disk thrashing. |
kPrefsCleanTicks = (UInt32) -(kPrefChangeWriteDelay+1) |
// When the preferences are clean we set gTicksOfLastPrefChange |
// to this value to prevent further writes from happening. |
// This corresponds to "far in the future". |
}; |
static UInt32 gTicksOfLastPrefChange = kPrefsCleanTicks; |
// The TickCount time at which we last modified the |
// preferences. If TickCount is more than this value plus |
// kPrefChangeWriteDelay, we need to save the preferences now. |
static void RestorePreferences(void) |
// Restore the preferences from Internet Config, |
// and put all the saved values back into the appropriate |
// state holder in the program. |
{ |
OSStatus err; |
PrefsRecord prefs; |
SInt32 prefsSize; |
ICAttr junkAttr; |
OTAssert("RestorePreferences: Need a main window to do this", gMainWindow != nil); |
OTAssert("RestorePreferences: Need a log list to do this", gLogList != nil); |
// Start by creating a connection to IC, only if it's present |
// (and we linked to it). |
err = noErr; |
if ( (void *) ICStart == (void *) kUnresolvedCFragSymbolAddress ) { |
err = -1; |
} |
if (err == noErr) { |
err = ICStart(&gICInstance, kCreator); |
} |
if (err == noErr) { |
err = ICFindConfigFile(gICInstance, 0, nil); |
} |
// Then read the preferences from IC. Ignore truncated errors. |
// It probably means that the preferences were last written by |
// a more modern version of this program, but it should damn well |
// make sure that our fields are the first fields of its preferences |
// record. |
if (err == noErr) { |
prefsSize = sizeof(PrefsRecord); |
err = ICGetPref(gICInstance, gICPrefsKey, &junkAttr, (void *) &prefs, &prefsSize); |
// err = -666; |
if (err == icTruncatedErr) { |
prefsSize = sizeof(PrefsRecord); |
err = noErr; |
} |
} |
// If the prefs record is too small, then look to see if matches |
// the prefs record of an older version of the program. If it does, |
// upgrade the prefs record by filling in the new fields. Note that |
// we donÕt dirty the preferences here. If the user dirties the |
// preferences for any other reason then weÕll write out an upgraded |
// prefs record. |
if (err == noErr && prefsSize < sizeof(PrefsRecord)) { |
if (prefsSize == sizeof(PrefsRecord0)) { |
prefs.startOnLaunch = false; |
} else { |
err = -2; |
} |
} |
// Basical sanity checks. |
if (err == noErr && prefs.magic != kPrefsMagic) { |
err = -3; |
} |
// If we could not read preferences, use defaults instead. |
if (err != noErr) { |
prefs.magic = kPrefsMagic; |
MoreGetWindowGlobalRect(gMainWindow, &prefs.mainWindowPosition); |
prefs.moduleID = 0; |
prefs.streamID = 0; |
prefs.logToFile = false; |
prefs.logErrors = true; |
prefs.logTraces = true; |
prefs.tracesMatching = false; |
prefs.mainWindowVisible = true; |
prefs.scrollWhileLogging = true; |
prefs.startOnLaunch = false; |
} |
// Now set up the program state from the preferences. |
gModuleID = prefs.moduleID; |
gStreamID = prefs.streamID; |
SetDialogControlBoolean(gMainWindow, ditLogToFile, prefs.logToFile); |
SetDialogControlBoolean(gMainWindow, ditLogErrors, prefs.logErrors); |
SetDialogControlBoolean(gMainWindow, ditLogTraces, prefs.logTraces); |
SetDialogControlBoolean(gMainWindow, ditTracesMatching, prefs.tracesMatching); |
SetDialogControlBoolean(gMainWindow, ditAllTraces, ! prefs.tracesMatching); |
SetDialogControlEnable(gMainWindow, ditSetupFilter, prefs.tracesMatching); |
MoveWindow(gMainWindow, prefs.mainWindowPosition.left, prefs.mainWindowPosition.top, false); |
SizeMainWindow(prefs.mainWindowPosition.right - prefs.mainWindowPosition.left, |
prefs.mainWindowPosition.bottom - prefs.mainWindowPosition.top); |
SetMainWindowVisibility(prefs.mainWindowVisible); |
SetScrollWhileLogging(prefs.scrollWhileLogging); |
SetStartOnLaunch(prefs.startOnLaunch); |
} |
static void SavePreferences(void) |
// Save the preferences to Internet Config. |
{ |
OSStatus junk; |
PrefsRecord prefs; |
// Don't even think about this if we don't have a connection |
// to IC. |
if ( gICInstance != nil ) { |
// Fill out the PrefsRecord from the program state |
// and write it out. |
prefs.magic = kPrefsMagic; |
MoreGetWindowGlobalRect(gMainWindow, &prefs.mainWindowPosition); |
prefs.moduleID = gModuleID; |
prefs.streamID = gStreamID; |
prefs.logToFile = GetDialogControlBoolean(gMainWindow, ditLogToFile); |
prefs.logErrors = GetDialogControlBoolean(gMainWindow, ditLogErrors); |
prefs.logTraces = GetDialogControlBoolean(gMainWindow, ditLogTraces); |
prefs.tracesMatching = GetDialogControlBoolean(gMainWindow, ditTracesMatching); |
prefs.mainWindowVisible = ((WindowPeek) gMainWindow)->visible; |
prefs.scrollWhileLogging = gScrollWhileLogging; |
prefs.startOnLaunch = gStartOnLaunch; |
junk = ICSetPref(gICInstance, gICPrefsKey, kICAttrNoChange, (void *) &prefs, sizeof(PrefsRecord)); |
OTAssert("SavePreferences: Error writing prefs", junk == noErr); |
} |
gTicksOfLastPrefChange = kPrefsCleanTicks; |
} |
static void DirtyPreferences(void) |
// Called by the general code when something that is |
// stored in the prefs is modified. We note the time |
// this happens, and the idle function should eventually |
// notice this and call SavePreferences. |
{ |
gTicksOfLastPrefChange = TickCount(); |
} |
static void PreferencesIdle(void) |
// Our idle function, called by DoIdle, once each time around |
// the event loop. We see how long the preferences have been dirty |
// and write them if it's been too long. |
{ |
if ( TickCount() > gTicksOfLastPrefChange + kPrefChangeWriteDelay) { |
SavePreferences(); |
} |
} |
static void TermPreferences(void) |
// This routine is called when the application quits |
// to save any preferences that haven't yet been saved by |
// our idle function and shut down our connection to IC. |
{ |
OSStatus junk; |
if ( gICInstance != nil ) { |
if ( gTicksOfLastPrefChange != kPrefsCleanTicks ) { |
SavePreferences(); |
} |
junk = ICStop(gICInstance); |
OTAssert("TermPreferences: Error closing IC", junk == noErr); |
gICInstance = nil; |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** Startup and Shutdown Code ***** |
static void InitMacToolbox(void) |
// Init all standard Mac OS managers. |
// How many times have I written this? |
{ |
InitGraf(&qd.thePort); |
InitFonts(); |
InitWindows(); |
InitMenus(); |
TEInit(); |
InitDialogs(nil); |
MaxApplZone(); |
InitCursor(); |
} |
// InitOpenTransportWithMemoryLimit Big Picture |
// -------------------------------------------- |
// |
// The LogEngine uses the OT memory allocation routines |
// (OTAllocMem, OTFreeMem) to allocate space for log entries in |
// the notifier. This memory comes from the OT client memory pool |
// that OT creates for us when we call InitOpenTransport. However, |
// this pool has some bad characteristics: |
// |
// 1. The pool starts off very small, and only grows when we allocate |
// memory from it. As we do all our allocation from our notifier |
// (which is interrupt time with respect to the system Memory Manager) |
// the pool can't grow immediately. So the pool will often run |
// be full (ie OTAllocMem will return nil) even though the application |
// has plenty of memory. The pool will later grow, but we've already |
// dropped the log entry on the floor. |
// |
// 2. Because the pool starts off small and grows by pieces, we get |
// an extremely fragmented pool. While this works, its definitely |
// sub-optimal. |
// |
// 3. If we're being hammered by strlog (ie people are calling strlog a |
// lot), the pool will keep growing and there's nothing to stop |
// the pool consuming our entire application heap. When it does so, |
// various toolbox routines (eg QuickDraw) fail ungracefully, ie |
// SysError(25). |
// |
// See DTS Technote 1128 "Understanding OT Memory Management" for more |
// details on OT client pools. |
// |
// <http://developer.apple.com/technotes/tn/tn1128.html> |
// |
// My old solution to this was to call ASLM to pre-create our client pool |
// in a dedicated zone. That way the pool could grow without running out |
// heap out of memory. ASLM allows you to control the initial size |
// of the pool, so I created a pool with a large initial size to avoid |
// the other problems described above. |
// |
// This solution had one big problem: it relies on ASLM. ASLM is going |
// away (and has been for many years). Taking a dependency on ASLM is |
// a bad thing. Also, relying on the fact that OT uses ASLM as its |
// underlying client pool mechanism is a bad idea because, even if ASLM |
// doesnÕt go away, thereÕs no guarantee that OT will use it. In fact, |
// a impending release of OT will not use ASLM at all, and thus will |
// implement its own client pool mechanism independent of the ASLM client |
// pool. |
// |
// This alternative solution is based on the concept of an OT memory |
// reserve. When we start the application we create a memory reserve |
// by allocating memory from the OT client pool. When we need memory we |
// try to get it from the client pool and, if that fails, free some our |
// memory reserve and try again. |
// |
// See "OTMemoryReserve.h" for more conceptual details about the OT memory |
// reserve module and how the following parameters fit into the scheme of |
// things. |
enum { |
kBytesReservedForToolboxInApplicationZone = 100L * 1024L, |
// This value represents the minimum number of |
// bytes that should remain in the application heap after |
// we've allocated the OT memory reserve. |
kOTMemoryReserveChunkSize = 128L * 1024L, |
// The chunk size to use when creating the OT memory reserve. |
kOTMemoryReserveMinChunks = 6, |
// The minimum number of chunks that we need to obtain if |
// weÕre going to run. This roughly corresponds to our |
// minimum application partition of 1 MB. |
kOTMemoryReserveMaxChunks = 100 |
// The maximum number of chunks that we need for our memory |
// pool. This multiplied by our chunk size comes out around |
// 12 MB. If we have more than 12 MB of strlog messages |
// lying around, something bad has happened. |
}; |
static OSStatus InitOpenTransportWithMemoryLimit(void) |
{ |
OSStatus err; |
OTClientContextPtr junkClientContext; |
err = InitOpenTransportInContext(kInitOTForApplicationMask, &junkClientContext); |
if (err == noErr) { |
err = InitOTMemoryReserve(kBytesReservedForToolboxInApplicationZone, kOTMemoryReserveChunkSize, |
kOTMemoryReserveMinChunks, kOTMemoryReserveMaxChunks, |
nil); |
if (err != noErr) { |
CloseOpenTransportInContext(nil); |
} |
} |
return err; |
} |
static OSStatus InitApplication(void) |
// Initialises the application-specific stuff. |
{ |
OSStatus err; |
OSStatus junk; |
Handle mbarH; |
MenuHandle menuH; |
Rect viewRect; |
Rect dataBounds; |
Cell cellSize; |
// Setup the menu bar. |
mbarH = GetNewMBar(rMenuBar); |
OTAssert("InitApplication: Could not get menu bar", mbarH != nil); |
SetMenuBar(mbarH); |
InvalMenuBar(); |
// Setup the Apple menu. |
menuH = GetMenuHandle(mApple); |
OTAssert("InitApplication: Could not get Apple menu", menuH != nil); |
AppendResMenu(menuH, 'DRVR'); |
// Install our AppleEvent handlers. |
junk = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(AEOpenApplicationHandler), 0, false); |
OTAssert("InitApplication: Could not install event handler", junk == noErr); |
junk = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(AEOpenDocumentsHandler), 0, false); |
OTAssert("InitApplication: Could not install event handler", junk == noErr); |
junk = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(AEQuitApplicationHandler), 0, false); |
OTAssert("InitApplication: Could not install event handler", junk == noErr); |
// Create the main window. |
gMainWindow = GetNewDialog(rMainDialog, nil, (WindowPtr) -1L); |
OTAssert("InitApplication: Could not create main window", gMainWindow != nil); |
gMainWindowGrowBounds.top = gMainWindow->portRect.bottom; |
gMainWindowGrowBounds.left = gMainWindow->portRect.right; |
gMainWindowGrowBounds.bottom = qd.screenBits.bounds.bottom; |
gMainWindowGrowBounds.right = qd.screenBits.bounds.right; |
// And the list that's in the main window. |
gListDefProcUPP = NewListDefProc(ListDefProc); |
OTAssert("InitApplication: Could not create UPP for ListDefProc", gListDefProcUPP != nil); |
GetDialogItemRect(gMainWindow, ditLogEntryList, &viewRect); |
viewRect.bottom -= 15; |
viewRect.right -= 15; |
SetRect(&dataBounds, 0, 0, 1, 0); |
// SetPt(&cellSize, viewRect.right - viewRect.left, 16); |
SetPt(&cellSize, 1000, 16); |
gLogList = LNew(&viewRect, // rView |
&dataBounds, // dataBounds |
cellSize, // cSize |
rSimpleLDEF, // theProc |
gMainWindow, // theWindow |
true, // drawIt |
true, // hasGrow |
true, // scrollHoriz |
true); // scrollVert |
OTAssert("InitApplication: Could not create ListHandle", gLogList != nil); |
(**gLogList).refCon = (long) gListDefProcUPP; |
gListUserItemUpdateUPP = NewUserItemProc(ListUserItemUpdateProc); |
OTAssert("InitApplication: Could not create UPP for ListUserItemUpdateProc", gListUserItemUpdateUPP != nil); |
SetDialogItemUserItemProc(gMainWindow, ditLogEntryList, gListUserItemUpdateUPP); |
// Restore our saved preferences, or setup defaults if |
// we have none. |
RestorePreferences(); |
// Sync the Start and Stop buttons with the state of |
// the back end. |
UpdateStartStopUI(); |
// Startup Open Transport. We have to do some special |
// things to prevent OT eating our entire heap. See |
// the comments associated with this routine. |
err = InitOpenTransportWithMemoryLimit(); |
return err; |
} |
static void TermApplication(void) |
// Shut down our application. This is normal quitting, |
// ie via AppleEvent or via menu command. The main event |
// loop has already shut down the logging stuff. All we |
// need to do is close down the connection to Open Transport, |
// ASLM and Internet Config. |
// |
// Note that we make no attempt to clean up our toolbox stuff |
// or memory allocates. The Process Manager will do that for us |
// and there's no point writing extra code (and extra bugs) to |
// duplicate that here. |
{ |
TermOTMemoryReserve(); |
CloseOpenTransportInContext(nil); |
TermPreferences(); |
OTAssert("TermApplication: Quitting with raw streams open!!!", ! LoggingActive() ); |
} |
// Raw streams are not tracked by Open Transport. When the |
// application quits abnormally, we have to make sure that |
// we have closed the stream, otherwise it will dangle around |
// in memory. This would not be too bad except: |
// |
// a) the notifier may be called, calling code that is no |
// no longer loaded, and |
// b) the log driver won't let another client hook up as |
// the stream logger as long as the stream is open. |
// |
// So we have a CFM terminate procedure that shuts down |
// the raw stream if it's open. |
// |
// Note that I don't recommend this approach for closing |
// endpoints and such. OT has automatic shutdown code, |
// and I recommend that you rely on it for emergency shutdown |
// in the general case. This code is only necessary because |
// we're using raw streams. |
// |
// Of course, for normal shutdown, ie the user chooses Quit, you |
// should not rely on OT's emergency shutdown support; you |
// should always call CloseOpenTransport yourself for normal |
// shutdown. |
extern pascal void EmergencyShutdown(void); |
extern pascal void EmergencyShutdown(void) |
{ |
if ( LoggingActive() ) { |
OTDebugStr("EmergencyShutdown: Had to StopLogging"); |
StopLogging(); |
} |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark **** The Main Line! ***** |
extern void main(void) |
{ |
OSStatus err; |
InitMacToolbox(); |
err = InitApplication(); |
OTAssert("main: InitApplication returned an error", err == noErr); |
if (err == noErr) { |
#if MORE_DEBUG |
if (true) { |
OTMemoryReserveTest(); |
} |
#endif |
MainEventLoop(); |
TermApplication(); |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-07-22