/* * Logging.c * * Utility functions for managing a log window * and writing formatted messages to the log. * * Written by: Ken McLeod, 2026-01-16 * Last update: 2026-01-31 * * This software is provided as-is, with no warranties expressed or implied, * under the terms of the BSD 2-Clause License. See separate "LICENSE" file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Logging.h" extern char *PAPFunctionNames[]; WindowPtr TextWindow = 0L; TEHandle TextTE = 0L; ControlHandle TextScrollBar = 0L; WindowRecord WRec; Boolean WindowActive = false; int gLogLevel = 0; int LogLevel(void) { return gLogLevel; } void SetLogLevel(int newLevel) { gLogLevel = newLevel; } Boolean InitLogWindow(int logWindowID, WindowPtr *window, TEHandle *textTE, ControlHandle *scrollBar) { Rect box, bounds; TextWindow = GetNewWindow(logWindowID, &WRec, (WindowPtr) -1L); *window = TextWindow; if (!TextWindow) { return false; } SetPort(TextWindow); TextSize(9); TextFont(1); box = TextWindow->portRect; box.left = 5; box.bottom -= 16; box.right -= 16; bounds.top = box.top; bounds.left = 5; bounds.right = box.right; bounds.bottom = 2000; TextTE = TENew(&bounds,&box); *textTE = TextTE; if (!TextTE) { return(false); } box = TextWindow->portRect; box.top--; box.right -= 15; bounds.top = box.top - 1; bounds.left = box.right; bounds.right = bounds.left + 16; bounds.bottom = box.bottom - 14; TextScrollBar = NewControl(TextWindow,&bounds,"\p",1,1,1,1,scrollBarProc,(long)TextTE); *scrollBar = TextScrollBar; if (!TextScrollBar) { return false; } BeginUpdate(TextWindow); EndUpdate(TextWindow); DrawControls(TextWindow); DrawGrowIcon(TextWindow); TEActivate(TextTE); WindowActive = true; AdjustTextScrollBar(); return true; } void UnloadLogWindow(void) { if (TextWindow) { CloseWindow(TextWindow); } } void UpdateLogWindow(void) { GrafPtr savePort; Rect box; GetPort(&savePort); SetPort(TextWindow); BeginUpdate(TextWindow); box = TextWindow->portRect; EraseRect(&box); TEUpdate(&box,TextTE); DrawControls(TextWindow); EndUpdate(TextWindow); DrawGrowIcon(TextWindow); SetPort(savePort); } void ActivateLogWindow(Boolean active) { WindowActive = active; if (WindowActive) { TEActivate(TextTE); } else { TEDeactivate(TextTE); } HiliteControl(TextScrollBar,(WindowActive) ? 0 : 255); } void GrowLogWindow(short h, short v) { MoveControl(TextScrollBar,h - 15,0); SizeControl(TextScrollBar,16,v - 15); HLock((Handle)TextTE); (**TextTE).viewRect.right = h - 15; (**TextTE).viewRect.bottom = v - 15; (**TextTE).destRect.right = h - 15; HUnlock((Handle)TextTE); TECalText(TextTE); AdjustTextScrollBar(); } void ShowLogWindow(Rect *r) { Rect newRect; //%%% should constrain r to be within qd.screenBits.bounds here MoveWindow(TextWindow, r->left, r->top, true); SizeWindow(TextWindow, r->right - r->left, r->bottom - r->top, true); newRect = ((GrafPtr)TextWindow)->portRect; GrowLogWindow(newRect.right,newRect.bottom); ShowWindow(TextWindow); } pascal void CtlAction(ControlHandle theControl, short part) { TEHandle TE; short newValue, value, v, max; TE = (TEHandle) GetCRefCon(theControl); max = GetCtlMax(theControl); HLock((Handle)TE); v = (**TE).lineHeight; HUnlock((Handle)TE); newValue = value = GetCtlValue(theControl); if (part == inPageUp) { newValue -= 12; if (newValue < 1) newValue = 1; SetCtlValue(theControl,newValue); } if (part == inPageDown) { newValue += 12; if (newValue > max) newValue = max; SetCtlValue(theControl,newValue); } if (part == inUpButton) { newValue--; if (newValue < 1) newValue = 1; SetCtlValue(theControl,newValue); } if (part == inDownButton) { newValue++; if (newValue > max) newValue = max; SetCtlValue(theControl,newValue); } if (value != newValue) TEScroll(0,(value - newValue) * v,TE); } void HandleMouseInText(EventRecord *myEvent) { short where; ControlHandle whichControl; short value, oldValue; short v; Rect box; SetPort(TextWindow); GlobalToLocal(&myEvent->where); where = FindControl(myEvent->where,TextWindow,&whichControl); switch (where) { case inUpButton: case inDownButton: case inPageUp: case inPageDown: TrackControl(whichControl,myEvent->where,(ProcPtr)CtlAction); break; case inThumb: HLock((Handle)TextTE); v = (**(TextTE)).lineHeight; HUnlock((Handle)TextTE); oldValue = GetCtlValue(whichControl); if (TrackControl(whichControl,myEvent->where,0L)) { value = GetCtlValue(whichControl); if (value != oldValue) { TEScroll(0,(oldValue - value) * v,TextTE); } } break; case 0: HLock((Handle)TextTE); box = (**(TextTE)).viewRect; HUnlock((Handle)TextTE); if (PtInRect(myEvent->where,&box)) TEClick(myEvent->where,(myEvent->modifiers & shiftKey),TextTE); break; default: break; } } void AdjustTextScrollBar(void) { short lines; short v; short top, bottom; HLock((Handle)TextTE); lines = (**TextTE).nLines; v = (**TextTE).lineHeight; top = (**TextTE).viewRect.top; bottom = (**TextTE).viewRect.bottom; HUnlock((Handle)TextTE); bottom -= top + 10; lines -= bottom / v; if (lines < 1) lines = 1; if (lines > 1) lines++; SetCtlMax(TextScrollBar,lines); if (lines > 1 && WindowActive) HiliteControl(TextScrollBar,0); else HiliteControl(TextScrollBar,255); } void Message(int level, char *mess) { long len; GrafPtr savePort; char st[40]; unsigned long t; int index, line; if ((level & 0x7FFF) > gLogLevel) { return; } /* always insert new text at end */ HLock((Handle)TextTE); index = (**TextTE).teLength; HUnlock((Handle)TextTE); TESetSelect(index,index,TextTE); GetDateTime(&t); GetPort(&savePort); SetPort(TextWindow); IUTimeString((long) t, true,st); while (st[0] < 14) { st[0]++; st[st[0]] = ' '; } len = st[0]; TEInsert(&st[1],len,TextTE); IUDateString((long) t, shortDate ,st); while (st[0] < 14) { st[0]++; st[st[0]] = ' '; } len = st[0]; TEInsert(&st[1],len,TextTE); /* if the high bit of level is set, add a log level prefix string */ if (level & 0x8000) { switch (level & 0x7FFF) { case 0: BlockMove("\pERROR: ",st,8); break; case 1: BlockMove("\pWARNING: ",st,10); break; case 2: BlockMove("\pINFO: ",st,7); break; case 3: BlockMove("\pDEBUG: ",st,8); break; default: st[0] = 0; break; } len = st[0]; TEInsert(&st[1],len,TextTE); } len = (long)mess[0] & 0x000000FF; TEInsert(&mess[1],len,TextTE); TEKey(0x0D,TextTE); /* add a carriage return */ TECalText(TextTE); /* Eventually we will run out of memory if we keep adding text, * so after we reach a hardcoded limit, remove a chunk of lines * from the top of the text edit record to make room for new text. * %%% TBD: limit should be calculated dynamically based on free memory */ index = 0; HLock((Handle)TextTE); len = (**TextTE).teLength; if (len > 16384) { line = (**TextTE).nLines / 4; /* cut first 25% of the lines */ index = (**TextTE).lineStarts[line-1]; TESetSelect(0,index,TextTE); TEDelete(TextTE); index = (**TextTE).teLength; TESetSelect(index,index,TextTE); TEScroll(0,(**TextTE).lineHeight * (line-2),TextTE); } HUnlock((Handle)TextTE); AdjustTextScrollBar(); CtlAction(TextScrollBar,inDownButton); /* adjust scroll down one line */ SetPort(savePort); } void MessageWithIntValue(int level, char *mess, long int value) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int len, vlen; char val[16]; char tmp[256]; BlockMove(mess, tmp, mess[0]+1); tmp[tmp[0]+1] = ' '; tmp[0] = tmp[0]+1; sprintf(val, "%ld", value); vlen = (int)(strlen(val) & 0xFF); len = 255 - (1 + mess[0] + 1); if (vlen < len) { len = vlen; } BlockMove(val, &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; Message(level,tmp); } } void MessageWithQuotedString(int level, char *mess, char *str) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int len; char tmp[256]; BlockMove(mess, tmp, mess[0]+1); tmp[0] = mess[0]; len = 255 - (1 + mess[0] + 1 + 1 + 1); if (str[0] < len) { len = str[0]; } BlockMove(" Ò",&tmp[tmp[0]+1],2); tmp[0] += 2; BlockMove(&str[1], &tmp[tmp[0]+1], len); tmp[0] += len; BlockMove("Ó", &tmp[tmp[0]+1], 1); tmp[0] += 1; Message(level,tmp); } } void MessageWithDataPointer(int level, char *mess, char *data, int dataLen) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int index, len; char tmp[256]; BlockMove(mess, tmp, mess[0]+1); tmp[tmp[0]+1] = ' '; tmp[0] = tmp[0]+1; len = 255 - (1 + mess[0] + 1); if (dataLen < len) { len = dataLen; } BlockMove(data, &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; for (index = mess[0]+2; index < tmp[0]+1; index++) { if (tmp[index] < 0x20 || tmp[index] > 0x7E) { tmp[index] = '.'; /* change non-printing ascii chars to a dot */ } } Message(level,tmp); } } void MessageWithNetAddr(int level, char *mess, short net, char node, char socket) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int len; char val[10]; char tmp[256]; len = 255 - (1 + 1 + 3 + 1 + 3 + 1 + 3); if (mess[0] < len) { len = mess[0]; } BlockMove(mess, tmp, len+1); tmp[tmp[0]+1] = ' '; tmp[0] = tmp[0]+1; sprintf(val, "%u", (unsigned int)net); len = (int)(strlen(val) & 0xFFFF); BlockMove(val, &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; tmp[tmp[0]+1] = '.'; tmp[0] = tmp[0]+1; sprintf(val, "%d", (int)node & 0xFF); len = (int)(strlen(val) & 0xFF); BlockMove(val, &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; tmp[tmp[0]+1] = ':'; tmp[0] = tmp[0]+1; sprintf(val, "%d", (int)socket & 0xFF); len = (int)(strlen(val) & 0xFF); BlockMove(val, &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; Message(level,tmp); } } void MessageReceivedFromNetAddr(int level, int funcID, Boolean isResp, short net, char node, char socket) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int len; char tmp[256]; BlockMove("\pGot ", tmp, 10L); len = (int)(strlen(PAPFunctionNames[funcID]) & 0xFFFF); BlockMove(&PAPFunctionNames[funcID][0], &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; len = (isResp) ? 14 : 13; BlockMove((isResp) ? " response from" : " request from", &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; MessageWithNetAddr(level,tmp, net, node, socket); } } void MessageSentToNetAddr(int level, int funcID, Boolean isResp, short net, char node, char socket) { if ((level & 0x7FFF) > gLogLevel) { return; } else { int len; char tmp[256]; BlockMove("\pSent ", tmp, 6L); len = (int)(strlen(PAPFunctionNames[funcID]) & 0xFFFF); BlockMove(&PAPFunctionNames[funcID][0], &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; len = (isResp) ? 12 : 11; BlockMove((isResp) ? " response to" : " request to", &tmp[tmp[0]+1], len); tmp[0] = tmp[0]+len; MessageWithNetAddr(level,tmp, net, node, socket); } }