_CTRACE: A MESSAGE LOGGING CLASS_ by William D. Cramer [LISTING ONE] /** CTrace.h -- Definitions for using the Trace class **/ #define _H_CTrace /* System/library header files */ #include /* standard menu command definition */ #include /* standard OOP definitions */ #include /* varg macro definitions */ #include /* definitions for desktop class */ #include /* definitions for menu bar manager */ #include /* definitions for data file class */ #include /* definitions for the application class */ #include /* definitions for parent class */ #include /* miscellaneous environment constants */ /* Local header files */ #include "CLogPanorama.h" /* definitions for logging panorama class */ /* Resource numbers */ #define TRACE_MENU_ID (2000) /* menu resource ID */ #define TRACE_MENU_SHOW (2000L) /* menu command for show/hide log */ #define TRACE_MENU_MASK (2001L) /* menu command for log masking */ #define TRACE_WINDOW_ID (2000) /* main window resource ID */ #define TRACE_MASK_DIALOG (2000) /* resource ID for dialog box */ #define FIRST_MASK (3) /* item # of first checkbox in dialog */ #define LAST_MASK (34) /* item # of last checkbox in dialog */ #define UNHILITE_CONTROL (255) /* magic part # for disabling control */ #define OKAY_BUTTON_ITEM (1) /* item # for the 'okay' button */ #define CANCEL_BUTTON_ITEM (2) /* item # for the 'cancel' button */ /* Standard trace categories */ #define T_ERROR (0x00000001) /* serious error */ #define T_WARNING (0x00000002) /* mildly serious problem */ #define T_INFO (0x00000004) /* news you can use */ #define T_FUNC_IN (0x00000008) /* function entry */ #define T_FUNC_OUT (0x00000010) /* function exit */ /* Other constants */ #define MAX_USER_BUFF (MAX_LOGREC_CHAR-19+1) /* max length of user message */ #define TRACE_DEFAULT_MASK (0L) /* initial trace mask */ /* External references */ extern CDesktop *gDesktop; /* the whole desktop view */ extern CApplication *gApplication; /* the application object */ extern CBartender *gBartender; /* the menu bar object */ extern OSType gSignature; /* application signature */ struct CTrace : CDocument { /* local instance variables */ CLogPanorama *itsLogPanorama; /* panorama for trace messages */ unsigned long currMask; /* currently enabled trace categories */ /* local class methods */ void ITrace(short records); void ToggleTraceWindow(void); void SetTraceMask (void); void Trace (unsigned long mask, char *format, ...); Boolean IsItVisible(void); /* inherited methods overriden */ void UpdateMenus (void); Boolean DoSaveAs (SFReply *macSFReply); Boolean Close (Boolean quitting); }; [LISTING TWO] /** CTrace.c -- Methods for the trace document class. **/ #include "CTrace.h" /* trace class parameters */ /** Global declaration **/ CTrace *gTrace; /* the one instance of this class */ /** ITrace() -- Initializes trace document object. **/ void CTrace::ITrace ( short records /* number of records before wrap */ ) { Rect frameRect; /* window frame */ CDocument::IDocument (gApplication, TRUE); itsWindow = new (CWindow); itsWindow->IWindow (TRACE_WINDOW_ID, FALSE, gDesktop, this); itsWindow->GetFrame (&frameRect); itsWindow->Move (gDesktop->bounds.right - frameRect.right - RIGHT_SMARGIN,gDesktop->bounds.top + TOP_SMARGIN); itsLogPanorama = new (CLogPanorama); itsLogPanorama->ILogPane (records, this, itsWindow); itsMainPane = itsLogPanorama; currMask = TRACE_DEFAULT_MASK; gTrace = this; } /** ToggleTraceWindow() -- Toggles visibility of trace window. **/ void CTrace::ToggleTraceWindow(void) { if (itsWindow->visible) itsWindow->Hide (); else { itsWindow->Show (); itsWindow->Select (); } } /** Close() -- Overrides normal document method by closing trace window. **/ Boolean CTrace::Close ( Boolean quitting /* ignored */ ) { itsWindow->Hide (); return (TRUE); } /** SetTraceMask -- Allows user to set/clear defined trace masks.**/ void CTrace::SetTraceMask (void) { int bitNum, /* bit number within the trace mask */ checkBoxState, /* state of a checkbox (0=unset,1=set) */ item, /* loop counter */ itemType, /* item type (4=button, 5=checkbox) */ whichItem; /* item number selected by user */ Handle itemStuff; /* handle to dialog item parameters */ Boolean done; /* loop-termination flag */ Str255 title; /* text associated with a dialog item */ Rect itemRect; /* rectangle surrounding a control */ DialogPtr maskDialog; /* structure for dialog box */ /* Pull up the mask dialog box out of the resource fork */ maskDialog = GetNewDialog (TRACE_MASK_DIALOG, NULL, (Ptr)(-1)); /* Run through checkboxes */ for (item=FIRST_MASK, bitNum=0; item<=LAST_MASK; item++, bitNum++) { GetDItem (maskDialog, item, &itemType, &itemStuff, &itemRect); GetCTitle ( (ControlHandle)itemStuff, title); PtoCstr ((char*)title); if (strcmp((char*)title, "Undefined") != 0) { checkBoxState = ((currMask&(1L< MAX_USER_BUFF) userBuff[MAX_USER_BUFF] = NULL; /* build log message and add it to the log */ GetDateTime (&timeSecs); Secs2Date (timeSecs, &dateRec); sprintf (traceBuff, "%02d/%02d/%02d--%02d:%02d:%02d %s", dateRec.month, dateRec.day, dateRec.year-1900, dateRec.hour, dateRec.minute, dateRec.second, userBuff); itsLogPanorama->AddString (traceBuff); } } /** IsItVisible() -- Returns 'visible' flag to update menu bar entries. **/ Boolean CTrace::IsItVisible(void) { return (itsWindow->visible); } /** UpdateMenus() -- Disables Save and Revert entries **/ void CTrace::UpdateMenus(void) { inherited::UpdateMenus ();; gBartender->DisableCmd (cmdSave); gBartender->DisableCmd (cmdRevert); } /** DoSaveAs() -- Writes out contents of itsLogList to indicated file. **/ Boolean CTrace::DoSaveAs ( SFReply *macSFReply /* the user's choice of file */ ) { char logRecBuff[MAX_LOGREC_CHAR]; /* buffer for log entry */ short maxRec, /* number of records in LogList */ offsetToNull, /* byte offset to end of log entry */ rec; /* loop counter */ /* Dispose of the data used for the old file record */ if (itsFile != NULL) itsFile->Dispose (); /* Set up the new data file (no error checking!) */ itsFile = new (CDataFile); ((CDataFile *)itsFile)->IDataFile (); itsFile->SFSpecify (macSFReply); itsFile->CreateNew (gSignature, 'TEXT'); itsFile->Open (fsRdWrPerm); /* Write out all records in list (add carriage return to end of each line).*/ maxRec = (short)(itsLogPanorama->itsLogList)->GetNumItems(); for (rec=1; rec<=maxRec; rec++) { (itsLogPanorama->itsLogList)->GetString (rec, logRecBuff); offsetToNull = strlen (logRecBuff); logRecBuff[offsetToNull] = '\r'; ((CDataFile*)itsFile)->WriteSome (logRecBuff, offsetToNull+1); } return (TRUE); } [LISTING THREE] /** CLogPanarama.h -- Definitions for using the LogPanorama class **/ #define _H_CLogPanorama /* System/library headers */ #include /* definitions for superclass Panorama */ #include /* definitions for ScrollPane class */ #include /* definitions for Window class */ #include /* standard OOP definitions */ #include /* miscellaneous look-n-feel paramaters */ #include /* numeric extrema */ /* Local headers */ #include "CLogList.h" /* definitions for LogList class */ #define LOGPANE_FONT (monaco) /* font family of text in the log window */ #define LOGPANE_FONT_SIZE (9) /* size of text in the log window */ #define LOGPANE_HORZ_SCROLL (5) /* units per horizontal scroll */ #define LOGPANE_VERT_SCROLL (1) /* units per vertical scroll */ #define LOGPANE_INSET (4) /* left margin for start of text */ /* Externals referenced */ extern RgnHandle gUtilRgn; /* drawing region */ struct CLogPanorama : CPanorama { /* local class instance data */ CLogList *itsLogList; /* the buffer for logged data */ /* local class methods */ void ILogPane (short records, CBureaucrat *aSupervisor, CWindow *anEnclosure); void AddString (char *theString); /* inherited methods overriden */ void Draw (Rect *theRect); }; [LISTING FOUR] /** CLogPanorama.c -- Methods for a CLogPanorama class object. **/ #include "CLogPanorama.h" /* defines log class */ /** ILogPanorama -- Initializes an instance of the log panorama class. **/ void CLogPanorama::ILogPane ( short records, /* number of records in LogList */ CBureaucrat *aSupervisor, /* in-charge for this panorama */ CWindow *aWindow /* window object to place pane into */ ) { FontInfo fontParms; /* paramaters of selected font */ short lineSpace, /* pixels per line */ charSpace; /* pixels per widest character */ Rect maxWindowRect, /* maximum growth of log window */ marginRect; /* inside margins of viewable area */ CScrollPane *theScrollPane; /* pane associated with panorama */ /* Set drawing parameters and adjust record size, if necessary. **/ aWindow->Prepare (); TextFont (LOGPANE_FONT); TextSize (LOGPANE_FONT_SIZE); GetFontInfo (&fontParms); lineSpace = fontParms.ascent+fontParms.descent+fontParms.leading; charSpace = fontParms.widMax; if ( ((long)records*(long)lineSpace) > (long)INT_MAX) records = INT_MAX / lineSpace; SetRect (&maxWindowRect, MIN_WSIZE, MIN_WSIZE, (MAX_LOGREC_CHAR * charSpace) + SBARSIZE, (records * lineSpace) + SBARSIZE); aWindow->SetSizeRect (&maxWindowRect); /* Initialize Panorama's ScrollPane, set scroll units to the defaulted ** values, and attach the Panorama to the ScrollPane. */ theScrollPane = new (CScrollPane); theScrollPane->IScrollPane(aWindow, this, 0, 0, 0, 0,sizELASTIC, sizELASTIC,TRUE, TRUE, TRUE); theScrollPane->FitToEnclFrame (TRUE, TRUE); theScrollPane->SetSteps (LOGPANE_HORZ_SCROLL, LOGPANE_VERT_SCROLL); /* Initialize Panarama to include maximum chars wide and maximum records tall, ** set the Panarama units to one char wide and one char tall. */ CPanorama::IPanorama(theScrollPane, aSupervisor, MAX_LOGREC_CHAR, records, 0, 0, sizELASTIC, sizELASTIC); SetScales (charSpace, lineSpace); FitToEnclosure (TRUE, TRUE); theScrollPane->InstallPanorama (this); /* Create the LogList and initialize. */ itsLogList = new (CLogList); itsLogList->ILogList (records); } /** Draw() -- Refreshes the visible portion of the window. **/ void CLogPanorama::Draw ( Rect *drawRect /* portion of window to refresh */ ) { short firstRec, /* record number of first visible line */ hScale, /* how many pixels wide is a character? */ lastRec, /* record number of last line */ totalRec, /* total number of records in LogList */ vScale; /* how many pixels tall is a line? */ register short currRow, /* window coordinates of current row */ rec; /* loop counter */ char buff[MAX_LOGREC_CHAR]; /* buffer for fetching log records */ /* First, translate draw rectangle to records. **/ GetScales (&hScale, &vScale); totalRec = (short)itsLogList->GetNumItems (); firstRec = (drawRect->top / vScale); if (firstRec == 0) firstRec = 1; lastRec = (drawRect->bottom / vScale) + 1; if (lastRec > totalRec) lastRec = totalRec; /* Refresh all of visible lines. **/ Prepare (); for (rec=firstRec, currRow=firstRec*vScale; rec<=lastRec; rec++, currRow+=vScale) { itsLogList->GetString (rec, buff); MoveTo (LOGPANE_INSET, currRow); DrawString (CtoPstr(buff)); } } /** AddString() -- Adds a new string to panorama. **/ void CLogPanorama::AddString ( char *theString /* null-terminated string to add */ ) { Rect frameRect; /* interior of current frame */ short listLimits, /* maximum the LogList will hold */ hSpan, /* the horizontal span of frame */ vSpan, /* the vertical span of frame */ hScale, /* horizontal pixels in panarama unit */ vScale; /* vertical pixels in panarama unit */ Point topRecord, /* LogList record number of top row */ bottomRecord, /* LogList record number of bottom row */ newRecord, /* LogList record number of new row */ currPosition, /* panorama coordinates of top/left frame */ recPosition; /* panorama coordinates of new string */ /* Add the record to the LogList. */ itsLogList->AddString (theString); /* Get coordinates of current frame and calculate tentative coordinates for newly added record. */ GetPosition (&currPosition); GetFrameSpan (&hSpan, &vSpan); GetScales (&hScale, &vScale); topRecord.v = currPosition.v + 1; bottomRecord.v = topRecord.v + vSpan - 1; newRecord.v = (short)itsLogList->GetNumItems (); newRecord.h = currPosition.h; /* Determine where we are in reference to bottom of screen and of list. **/ if (newRecord.v > (bottomRecord.v+1) ) { /* bottom record isn't visible */ currPosition.v = newRecord.v - vSpan; ScrollTo (currPosition, FALSE); GetInterior (&frameRect); Prepare (); EraseRect (&frameRect); } else { /* bottom record is visible */ listLimits = itsLogList->GetMaxRecordCount (); if (bottomRecord.v < listLimits) { /* room in list--create blank line if necessary */ if (newRecord.v == (bottomRecord.v + 1) ) { Scroll (0, 1, TRUE); SetRect (&frameRect, newRecord.h*hScale, (newRecord.v-1)*vScale,(newRecord.h+hSpan)*hScale, (newRecord.v)*vScale); } else SetRect (&frameRect, newRecord.h*hScale, (newRecord.v-1)*vScale, (newRecord.h+hSpan-1)*hScale, (newRecord.v)*vScale); } else if (bottomRecord.v > listLimits) { currPosition.v = newRecord.v - vSpan; ScrollTo (currPosition, FALSE); GetInterior (&frameRect); Prepare (); EraseRect (&frameRect); } else { /* bottom of pane=limit of list, so do our own scrolling */ Prepare (); GetInterior (&frameRect); ScrollRect (&frameRect, 0, -vScale, gUtilRgn); SetRect (&frameRect, bottomRecord.h*hScale, (bottomRecord.v-1)*vScale,(bottomRecord.h+hSpan-1)*hScale, (bottomRecord.v)*vScale); EraseRect (&frameRect); } } Draw (&frameRect); itsScrollPane->Calibrate(); } [LISTING FIVE] /** CLogList.h -- Definitions for a LogList object. **/ #define _H_CLogList #include /* definitions for superclass */ #include /* standard OOP definitions */ #include /* miscellaneous string definitions */ #define MAX_LOGREC_CHAR (200L) /* size of longest entry (inc NULL) */ struct CLogList : CList { /* internal instance data */ short maxRec; /* maximum number of records */ /* local class methods */ void ILogList (short records); void AddString (char *theString); void GetString (short which, char *theString); short GetMaxRecordCount (void); /* inherited methods overriden */ void Dispose (void); }; [LISTING SIX] /** CLogList.c -- Methods for a LogList object. **/ #include "CLogList.h" /* definitions for LogList class */ /** ILogList -- Initializes a LogList for the indicated number of entries. **/ void CLogList::ILogList ( short records /* maximum number of entries */ ) { CList::IList (); maxRec = records; } /** Dispose -- Frees all records (and their handles) in the list **/ void CLogList::Dispose (void) { short i; /* loop counter */ Handle record; /* handle to list record */ while (GetNumItems() > 0) { record = (Handle)FirstItem(); Remove ((CObject*)record); DisposHandle (record); } } /** AddString -- Adds a string to LogList. **/ void CLogList::AddString ( char *theString /* pointer to null-terminated string */ ) { Handle record; /* handle for a list entry */ if (strlen(theString)+1 < MAX_LOGREC_CHAR) { record = NewHandle (strlen(theString)+1); strcpy (*record, theString); } else { record = NewHandle (MAX_LOGREC_CHAR); strncpy (*record, theString, MAX_LOGREC_CHAR); *record[MAX_LOGREC_CHAR-1] = NULL; } Append ((CObject*)record); if (GetNumItems () > maxRec) { record = (Handle)FirstItem (); Remove ((CObject*)record); DisposHandle (record); } } /** GetString -- Grabs requested entry and copies it to user's buffer. **/ void CLogList::GetString ( short which, /* record number to return */ char *theString /* point to destination buffer */ ) { Handle record; /* handle for a list entry */ if ( (record=(Handle)NthItem(which)) != NULL) strcpy (theString, *record); else theString[0] = 0; } /** GetMaxRecordCount -- Returns max.number of records available in LogList **/ short CLogList::GetMaxRecordCount (void) { return (maxRec); } [LISTING SEVEN] /** CMyApp.c -- Demonstrates how to include the CTrace utility as part of your ** application. Functions it performs are showing/hiding the trace log window ** and setting the trace mask. To demonstrate the calls to Trace(), ** it uses the New and Open menu commands. **/ #include /* Application class definitions */ #include /* Bartender class definitions */ #include /* standard command definitions */ #include "CTrace.h" /* Trace log class definitions */ /* external references */ extern CBartender *gBartender; extern CTrace *gTrace; /* Declare the application class */ struct CMyApp : CApplication { CTrace *itsTraceLog; void IMyApp (void); void DoCommand(long c); void UpdateMenus(void); }; /** ITestApp -- Initializes the application and the CTrace object. **/ void CTestApp::ITestApp(void) { CApplication::IApplication (4, 20480L, 2048L); itsTraceLog = new (CTrace); itsTraceLog->ITrace (100); } /** ITestApp -- Updates the Trace portion of the menus. **/ void CTestApp::UpdateMenus (void) { inherited::UpdateMenus (); gBartender->EnableMenu (TRACE_MENU_ID); gBartender->EnableCmd (TRACE_MENU_SHOW); gBartender->EnableCmd (TRACE_MENU_MASK); if (itsTraceLog->IsItVisible ()) gBartender->CheckMarkCmd (TRACE_MENU_SHOW, TRUE); else gBartender->CheckMarkCmd (TRACE_MENU_SHOW, FALSE); } /** ITestApp -- Processes application commands. **/ void CTestApp::DoCommand (long command) { int i; /* a loop counter */ static int addno=1; /* a counter for the demo adds */ switch (command) { case cmdNew : /* trace one value at mask TRACE_INFO */ gTrace->Trace (T_INFO, "one'sies add, data=%d", addno++); break; case cmdOpen : /* trace 32 messages, one at each mask value */ for (i=0; i<32; i++) gTrace->Trace ((1L<ToggleTraceWindow (); break; case TRACE_MENU_MASK : itsTraceLog->SetTraceMask (); break; default : inherited::DoCommand (command); } } /** main() -- Main routine of the demo program. **/ void main () { gApplication = new (CTestApp); ((CTestApp*)gApplication)->ITestApp (); gApplication->Run (); gApplication->Exit (); }