Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Ctrace: a Message Logging Class


NOV90: CTRACE: A MESSAGE LOGGING CLASS

CTRACE: A MESSAGE LOGGING CLASS

A tool that augments your development environment

William D. Cramer

Bill is a software engineering project manager for IEX Corp., a Plano, Texas engineering and consulting firm. He can be reached via e-mail at uunet!iex!cramer.


If you're like me, you've probably had some exposure to more traditional development environments on more traditional platforms. You know the sort of environment I mean -- a top-to-bottom program running on a dumb-terminal or PC. The kind of platform where just a few well-placed printf( )s can mean the difference between taking two minutes to pinpoint an error and taking two hours.

Most Macintosh programs provide the user with a very visual, very interactive interface. When developing such applications, you might test a particular feature by playing the role of the user -- if you see an anomaly, you use the Think C debugger to set a few key breakpoints, and then repeat your steps until you hit one of them. At that point, you can poke around until you find some stray pointer or uninitialized variable and correct the problem.

However, there's a whole set of problems that don't lend themselves to this kind of debugging. For example, actions involving a good number of calculations, iterations, or deeply embedded function calls -- all based on a single user action -- add complexity if a problem occurs. Then, too, there are operations that rely on certain timing constraints: If the user is expected to respond within a given time period, for example, debugging with breakpoints can ruin the whole timing frame. Finally, programs that deal with a non-human interface (such as AppleTalk or MacTCP) can cause no end of heartache when that external interface doesn't play fair.

This article describes a simple tool that augments your development environment with a general-purpose message logging window. The tool, built out of existing Think Class Library objects, furnishes you with some basic printf( ) capabilities and lets you define categories of messages. By enabling and disabling these categories, you can selectively control which events get logged.

What is CTrace?

CTrace evolved from a Unix-based tool I wrote several years ago. That version in turn evolved from various logging mechanisms found in systems-level daemons. What I've tried to do is extract the best of these older versions and add on a Mac flavor.

The CTrace class consists of three major elements -- a control mechanism through which you submit messages for conditional logging, a circular buffer containing your logged messages, and a display window that gives you a view of the buffer.

The logging control mechanism, a subclass of CDocument, uses a bit mask that defines up to 32 event categories. I've included some basic categories --errors, warnings, general information, and function entry/exit -- and you can add your own to fit your application. Your application submits requests to trace with a call like that in Example 1.

Example 1: Request to trace

  gTrace->Trace (T_INFO, "Added '%s' to the display list",
  subject);

When Trace( ) receives this message, it compares the mask passed as a parameter (in this example, T_INFO) with its internally stored mask. If you have enabled that particular category, Trace( ) writes your data (along with a time stamp) into the log buffer. Otherwise, it happily ignores the request and returns.

The log buffer, based on the Think C class CList, stores some finite number of text strings. When it reaches the predefined limit, it begins overwriting the oldest entries with the newest. A scan of the list will always show the contents from the earliest entry to the most recent entry.

The display window shows the contents of the circular buffer. Because the buffer can potentially contain thousands of messages, the display window uses a subclass of the CPanorama class enclosed within a CWindow. Hence, you can scroll to any part of the buffer, you can grow and shrink the window, and you can hide the window when you don't need it. The buffer and window operate independently of each other, so even when you have hidden the window, logging to the buffer continues. The window looks like the one shown in Figure 1. You enable and disable categories via a modal dialog conjured up from a command on the Trace menu. It presents you with a dialog box like the one shown in Figure 2. You can set the trace mask at any time. For example, you may want to leave all categories disabled until you reach a point where you want to examine a particular string of events. Just before starting the transaction, summon up the dialog, make the appropriate changes, and continue.

The Gory Details

I won't dwell too much on the implementation of the code -- it contains a plethora of comments and is built out of standard Think C core classes -- but I do want to describe a bit of its family heritage so that you can understand how to include CTrace in your programs. The tool consists of three classes, CTrace (see CTrace.h in Listing One, page 116 and CTrace.c in Listing Two, page 116), CLogPanorama (see CLogPanorama.h in Listing Three, page 118, and CLogPanorama.c in Listing Four, page 118), and CLogList (see Listing Five, page 120, for CLogList.h and Listing Six, page 120, for CLogList.c). Of these, CTrace is specific to this tool, while CLogPanorama and CLogList are fairly generic.

CTrace is a subclass of the CDocument class that contains two local instance variables:

  • currMask, the bit mask describing which categories are enabled and which are disabled.
  • itsLogPanorama, a CLogPanorama that displays the log messages.
The class defines five local methods:

  • ITrace( ) initializes the object to contain some maximum number of log records.
  • ToggleTraceWindow( ) shows and hides itsWindow (itsWindow is a CWindow object inherited from CDocument).
  • SetTraceMask( ) presents a dialog for enabling and disabling individual trace mask bits.
  • Trace( ) receives a user message and category from the application and conditionally adds it to its buffer and window. The class defines an external, gTrace, which points to the class, so that code can reference this method directly as gTrace->Trace( ) instead of using several levels of indirection via the application.
  • IsItVisible( ) returns the state of itsWindow so that the application can update its menus appropriately.
It overrides three inherited methods:

  • UpdateMenus( ) disables some of the non-applicable CDocument methods.
  • DoSaveAs( ) copies the buffer contents to a file.
  • Close( ) hides the window but does not actually close the CTrace document.
CLogPanorama is a subclass of the CPanorama class that contains a single, local instance variable:

  • itsLogList, a CLogList circular buffer that contains the logged messages.
It defines two local methods:

  • ILogPane initializes the panorama and installs it in the owner's window (in this case, CTrace's itsWindow).
  • AddString inserts an ASCII string into itsLogList. When the window is visible, it also draws the string into the panorama and does whatever is required to make sure that the newly displayed line is visible. This involves a bit of special case code; refer to the inline comments for more elaborate details.
The class overrides one of its inherited methods using Draw( ) to draw the visible portion of the buffer text into the panorama.

CLogList, based on CList, implements a circular buffer. It contains maxRec, a single, local instance variable that defines the maximum number of records allowed in the list and five local methods:

  • ILogList( ) initializes the list object.
  • AddString( ) appends the string to the end of the list. If the list has grown to its maximum, the method also deletes the first record in the list. Internally, the method allocates buffer space dynamically using handles on an as-needed basis.
  • GetString( ) returns a copy of a particular entry in the list.
  • GetMaxRecordCount( ) returns maxRec.
Finally, CLogList overrides the inherited method using Dispose( ) to delete all of the records in the list.

Using Trace

Like most classes, CTrace requires a bit of preparation to use in your program. First, add the resources in Table 1 to your application (you may change the resource IDs as required, but be sure to update the references in the header files appropriately). Within your code, you'll need to add an instance like that in Example 2 of the CTrace class to your application class.

Table 1: Added/modified resources

  Resources ID  Type  Description
  -------------------------------------------------------------------------

  2000          MENU  dtitled Trace, with the items Show (command 2000) and
                      Mask (command 2001).

  2000          DITL  buttons OK and Cancel (items 1 and 2), checkboxes
                      Errors, Warnings, Info, Func entry, and Func exit
                      (items 3 through 7), plus 27 additional checkboxes

                      (items 8 through 34) titled Undefined and marked as
                      disabled.

  2000          DLOG  coordinates (14,34),(508,286), procID 1, marked as
                      visible, and linked to DITL 2000.

  2000          WIND  titled Trace Log at coordinates (16,52)(330,220),
                      procID 8, marked not visible, with the goAwayFlag
                      enabled.

  1             MBAR  add menu res ID 2000

Example 2: Adding CTrace to CApplication

  struct CMyApp : CApplication
         {
         CTrace   *itsTraceLog;
         ...
         };

In your IApplication( ) method initialize itsTraceLog with the required number of records. For example, the code in Example 3 prepares CTrace to accept 1000 entries before wrapping. In your application's UpdateMenus( ) method, add the statements in Example 4. Finally, add the commands in Example 5 to your application's DoCommand( ) method.

Example 3: Initializing CTrace

  itsTraceLog = new (CTrace);
  itsTraceLog->ITrace (1000);

Example 4: Additions to UpdateMenus( ) method

  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);

Example 5: Additions to DoCommand( ) method

  case TRACE_MENU_SHOW :
            itsTraceLog- >ToggleTraceWindow ( );
            break;
  case TRACE_MENU_MASK :
            itsTraceLog- >SetTraceMask( );
            break;

To use CTrace effectively, you'll need to define masks for your logging categories. As you can see from the CTrace.h listing, I've defined five trace masks:

  • T_ERROR for logging serious errors that may prove fatal.
  • T_WARNING for logging problems that are probably not fatal.
  • T_INFO for logging valuable runtime information.
  • T_FUNC_IN for logging function or method entry (I usually include parameters passed into the function as arguments to Trace( )).
  • T_FUNC_OUT for logging exit from functions or methods (I usually include the return value, if applicable, as an argument to Trace( )).
You can add your mask definitions directly to the CTrace.h file, or you may want to define them in a separate header. In any case, define each as a unique long word with one bit set as shown in
Example 6. You'll also want to edit the tracemask DITL resource to put meaningful names on the set mask dialog box. A couple of comments may be appropriate here. First, the mask can contain up to 32 categories, but your application may not require this many. You may want to resize and rearrange the dialog box to show only those categories that you have defined. Note that DITL item #3 corresponds to mask 0x000000001, DITL #4 to mask 0x00000002, and so on. Also note that the dialog logic disables access to any items titled "Undefined." This prevents you from enabling categories for which there is no corresponding mask.

Example 6: Adding mask definitions

  #define T_PICT_REDRAW   (0x00002000L) /* screen refresh logging */

At this point, you can begin embedding log requests in your code. I have no great advice to give you with regard to when and where to place Trace( ) statements in your code. I usually put them at the entry and exit points in functions, and, depending on the complexity of the code, before or after vital actions. And you should, of course, include them in your error checking logic.

One note with regard to the content of your log messages -- the method uses varargs. Hence, in theory you can have as many arguments as you wish. Internally, however, the method has some self-preservation safeguards -- if you try to form a message longer than 200 characters, it will truncate your message back to this limit.

Hide It or Remove It?

Once you've completed all of the coding and debugging for your program, you'll no doubt want to hide CTrace from the end-user. You could run through all of your code and purge all references to CTrace, but it's really much simpler to edit the MBAR resource so that the Trace menu entry doesn't show up on the menu bar. And, because the default trace mask has no categories enabled, you won't be using up much additional memory (remember that CLogList allocates buffer space dynamically). You could make an argument that all of those Trace( ) calls will add unnecessary overhead to your program; I won't disagree that it adds some overhead, but because Trace( ) returns immediately if it doesn't see a match between the passed mask and its internal mask, you're really looking at only a handful of machine instructions per call.

Keeping CTrace in the wings also enhances your support capabilities if you have friendly users -- if some anomaly arises in the field, CTrace is only a ResEdit away.

Customizing CTrace

CTrace is ready for use out of the box. This is not to say that it doesn't have room for improvement. A short list of possible extensions includes the following:

  • When the CTrace methods allocate internal memory, they make the rash assumption that memory will be available. My own version of CTrace includes a call to CheckAllocation( ) around every NewHandle( ), but I left this out of the version described here because you probably have your own strategies for dealing with low memory.
  • A clever macro could perform the mask compare inline and save the expense of unnecessary function calls. I'm more pragmatic than clever, so I haven't spent the time developing such a macro.
  • The log pane uses simple Quick-Draw DrawString( ) commands to place text into the panorama. This limits the number of records you can store in the buffer to about 2700 records. If this proves insufficient, you may choose to modify the methods used by CLogPanorama to circumvent this limit.
  • One of the predecessors of this utility embedded the file and the line number of the calling program as part of the logged message. Think C supports the necessary macros (_FILE_ and _LINE_, respectively), but I chose not to include them in this version. You can certainly add them to your version.
  • While CTrace supports the DoSave As( ) CDocument method, it does so only on request. A nice extension to CTrace may include writing the log message to a file as well as the CLogPanorama. This may come in handy if your programs have the nasty habit of leaving your Mac in some altered state!
  • Finally, while I've done nothing to inhibit use of the inherited DoPrint( ) method, I've also done nothing to enhance it. If you have a need to produce pretty, formatted printouts of the trace log, you can override the inherited PrintPageOfDoc( ) method.
_CTRACE: A MESSAGE LOGGING CLASS_ by William D. Cramer

[LISTING ONE]

<a name="023b_0011">

/** CTrace.h -- Definitions for using the Trace class **/
#define _H_CTrace

/* System/library header files */
#include <Commands.h>      /* standard menu command definition */
#include <oops.h>      /* standard OOP definitions */
#include <stdarg.h>      /* varg macro definitions */
#include <CDesktop.h>      /* definitions for desktop class */
#include <CBartender.h>    /* definitions for menu bar manager */
#include <CDataFile.h>      /* definitions for data file class */
#include <CApplication.h>   /* definitions for the application class */
#include <CDocument.h>      /* definitions for parent class */
#include <Constants.h>      /* 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);
   };



<a name="023b_0012"><a name="023b_0012">
<a name="023b_0013">
[LISTING TWO]
<a name="023b_0013">

/** 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<<bitNum)) == 0L) ? 0 : 1;
      SetCtlValue ( (ControlHandle) itemStuff, checkBoxState);
      }
   else
      HiliteControl (itemStuff, UNHILITE_CONTROL);
   }
/* The default button (#1) is the okay button, draw outline around it. */
GetDItem (maskDialog, OKAY_BUTTON_ITEM, &itemType, &itemStuff, &itemRect);
SetPort (maskDialog);
PenSize (3, 3);
InsetRect (&itemRect, -4, -4);
FrameRoundRect (&itemRect, 16, 16);

/* Get events from dialog manager and process accordingly */
done = FALSE;
while (!done)
   {
   ModalDialog (NULL, &whichItem);
   GetDItem (maskDialog, whichItem, &itemType, &itemStuff, &itemRect);
   switch (itemType)
      {
      case ctrlItem + btnCtrl :   /* CANCEL or OKAY */
         if (whichItem == OKAY_BUTTON_ITEM)
      {
      currMask = 0L;
        for (item=FIRST_MASK, bitNum=0; item<=LAST_MASK; item++, bitNum++)
      {
      GetDItem (maskDialog, item, &itemType, &itemStuff, &itemRect);
           checkBoxState = GetCtlValue ( (ControlHandle) itemStuff);
            currMask |= (checkBoxState==0) ? 0L : (1L<<bitNum);
      }
      done = TRUE;
      }
      else if (whichItem == CANCEL_BUTTON_ITEM)
      done = TRUE;
      break;
   case ctrlItem + chkCtrl :   /* a category checkbox */
      checkBoxState = GetCtlValue ( (ControlHandle) itemStuff);
         if (checkBoxState == 0)
      SetCtlValue ( (ControlHandle) itemStuff, 1);
         else
      SetCtlValue ( (ControlHandle) itemStuff, 0);
         break;
      default:
         break;
      }
   }

/* On exit, trash dialog record and controls */
DisposDialog (maskDialog);
}

/** Trace() -- Checks current mask **/
void CTrace::Trace
   (
   unsigned long mask,      /* severity of message */
   char *format,         /* format for user's arguments */
   ...            /* arguments to format (varg) */
   )
{
static char
   traceBuff[MAX_LOGREC_CHAR],   /* string that will be added to log */
   userBuff[MAX_LOGREC_CHAR*2],   /* user's contribution to log record */
   prefix[40];         /* date+time string */
int
   maxUserBuff;      /* maximum length of formatted user string */
long
   timeSecs;      /* current time/date */
DateTimeRec
   dateRec;      /* time/date in MM/DD/YY HH:MM:SS components */
/* Should the message get added to the trace log? */
if ( (currMask & mask) != 0)
   {
   /* format the user's portion of the message */
   vsprintf (userBuff, format, __va(format));
   /* make sure the entire record will fit into traceBuff */
   if (strlen (userBuff) > 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);
}




<a name="023b_0014"><a name="023b_0014">
<a name="023b_0015">
[LISTING THREE]
<a name="023b_0015">

/** CLogPanarama.h -- Definitions for using the LogPanorama class **/
#define _H_CLogPanorama

/* System/library headers */
#include <CPanorama.h>      /* definitions for superclass Panorama */
#include <CScrollPane.h>   /* definitions for ScrollPane class */
#include <CWindow.h>      /* definitions for Window class */
#include <oops.h>      /* standard OOP definitions */
#include <Constants.h>      /* miscellaneous look-n-feel paramaters */
#include <Limits.h>      /* 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);
   };




<a name="023b_0016"><a name="023b_0016">
<a name="023b_0017">
[LISTING FOUR]
<a name="023b_0017">

/** 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();
}



<a name="023b_0018"><a name="023b_0018">
<a name="023b_0019">
[LISTING FIVE]
<a name="023b_0019">

/** CLogList.h -- Definitions for a LogList object. **/
#define _H_CLogList

#include <CList.h>      /* definitions for superclass */
#include <oops.h>      /* standard OOP definitions */
#include <string.h>      /* 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);
   };



<a name="023b_001a"><a name="023b_001a">
<a name="023b_001b">
[LISTING SIX]
<a name="023b_001b">

/** 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);
}




<a name="023b_001c"><a name="023b_001c">
<a name="023b_001d">
[LISTING SEVEN]
<a name="023b_001d">

/** 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 <CApplication.h>      /* Application class definitions */
#include <CBartender.h>       /* Bartender class definitions */
#include <Commands.h>         /* 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<<i), "Entry #%d, trace mask #%d (mask=0x%08lX)",addno++, i+1, (long)(1L<<i));
     break;
   case TRACE_MENU_SHOW :
      itsTraceLog->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 ();
}












Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.