AmendHub

Download:

ftech

/

Logger

/

amendments

/

6

v1.0 - Implemented console + refactored code to feature camel case syntax.


Francois Techene made amendment 6 5 days ago
--- logger-test.c Tue Apr 8 12:16:18 2025 +++ logger-test.c Sun Mar 29 16:42:19 2026 @@ -20,13 +20,64 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include "logger.h" +#define APP_NAME "AppTemplate" + +#define VERS_RES_ID 1 + +#define ABOUT_DIALOG_ID 130 +#define ABOUT_OK_BTN_ID 1 + +#define MBAR_ID 128 + +#define APPLE_MENU_ID 128 +#define APPLE_MENU_ABOUT_ID 1 + +#define FILE_MENU_ID 129 +#define FILE_MENU_NEW_ID 1 +#define FILE_MENU_QUIT_ID 2 + +void drawHeader(void); +void testLogLevels(void); +void runTests(void); + +void loadMenuBar(void); +void setMenuDefaults(void); +short onMenuClick(long menu_id); +void doEvent(EventRecord event); + +void centerWindow(WindowPtr win); + +Rect drawBtn(int h, int v, Str255 str); +void drawWindow(void); +void showAbout(void); + +WindowPtr mainWindow; +DialogPtr dialog; + +MenuHandle appleMenu; +MenuHandle fileMenu; + +Rect testBtn; +Rect printBtn; +Rect logBtn; +Rect errBtn; +Rect warnBtn; +Rect infoBtn; +Rect debugBtn; + +Boolean isReady = false; +Boolean quitting = false; +Boolean isFirstUpdate = true; + + void -draw_header(void) +drawHeader(void) { - logger.log(" \n\n"); + logger.log(" \r\r"); logger.log(" ____________________ "); logger.log(" | __________________ | "); logger.log(" | | | | "); @@ -48,96 +99,447 @@ draw_header(void) logger.log("/ /__| (_) | (_| | (_| | __/ | "); logger.log("\\____/\\___/ \\__, |\\__, |\\___|_| "); logger.log(" |___/ |___/ "); - logger.log("\n"); + logger.log("\r"); } void -test_log_levels(void) +testLogLevels(void) { - logger.log_level = NONE_LEVEL; + logger.logLevel = NONE_LEVEL; - logger.log(""); - logger.log("#### Testing NONE_LEVEL: Must not show any log:"); + logger.print("\r#### Testing NONE_LEVEL: Must not show any log:\r"); logger.debug("This debug log should not be visible"); logger.info("This info log should not be visible"); logger.warn("This warning log should not be visible"); logger.error("This error log should not be visible"); - logger.log_level = ERROR_LEVEL; + logger.logLevel = ERROR_LEVEL; - logger.log(""); - logger.log("#### Testing ERROR_LEVEL:"); + logger.print("\r#### Testing ERROR_LEVEL:\r"); logger.debug("This debug log should not be visible !!!"); logger.info("This info log should not be visible !!!"); logger.warn("This warning log should not be visible !!!"); logger.error("This error log should be visible"); - logger.log_level = WARNING_LEVEL; + logger.logLevel = WARNING_LEVEL; - logger.log(""); - logger.log("#### Testing WARNING_LEVEL:"); + logger.print("\r#### Testing WARNING_LEVEL:\r"); logger.debug("This debug log should not be visible !!!"); logger.info("This info log should not be visible !!!"); logger.warn("This warning log should be visible"); logger.error("This error log should be visible"); - logger.log_level = INFO_LEVEL; + logger.logLevel = INFO_LEVEL; - logger.log(""); - logger.log("#### Testing INFO_LEVEL:"); + logger.print("\r#### Testing INFO_LEVEL:\r"); logger.debug("This debug log should not be visible !!!"); logger.info("This info log should be visible"); logger.warn("This warning log should be visible"); logger.error("This error log should be visible"); - logger.log_level = DEBUG_LEVEL; - logger.log(""); - logger.log("#### Testing DEBUG_LEVEL:"); + logger.logLevel = DEBUG_LEVEL; + + logger.print("\r#### Testing DEBUG_LEVEL:\r"); logger.debug("This is a debug message with number %d ", 1); logger.info("This is an info message with number %d ", 2); logger.warn("This is a warning with number %d ", 3); logger.error("This is an error with number %d ", 4); } -int -main(void) + +void +runTests(void) { - init_logger(DEBUG_LEVEL, CONSOLE_OUT, NULL); - draw_header(); +#ifdef USE_LOGGER // Fail safe + LogConsoleScrollToBottom(); +#endif + + drawHeader(); + testLogLevels(); + logger.log("\rDone!"); +} + + +int main(void) +{ + EventRecord event; - init_logger(DEBUG_LEVEL, NONE_OUT, NULL); + short dlgItem; - logger.debug("This debug log should not be visible !!!"); - logger.info("This info log should not be visible !!!"); - logger.warn("This warning log should not be visible !!!"); - logger.error("This error log should not be visible !!!"); + short eventMask = mDownMask + + mUpMask + + keyDownMask + + keyUpMask + + autoKeyMask + + updateMask; + SetEventMask(everyEvent); // So we can get keyUp event. + FlushEvents(eventMask, 0); + InitGraf(&thePort); + InitFonts(); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(0); + InitCursor(); + MaxApplZone(); + // Loa menus + // + loadMenuBar(); - // You can use a file in a shared folder on the network - // and use "tail -f" on the remote machine the see the logs - // Comment the next init_log if you want to use this one. - // - init_logger(DEBUG_LEVEL, FILE_OUT, "Shared:Mac Dev:Logger:test.log"); - - // This path is relative to the project's folder - // - init_logger(DEBUG_LEVEL, FILE_OUT, "test.log"); - - - printf("Logging to file: %s ...\n", logger.file_path); - - draw_header(); - test_log_levels(); + // Init Window + // + mainWindow = GetNewWindow(128, 0L, (WindowPtr)-1L); + drawWindow(); + dialog = NULL; - printf("Done! Check the logs in the log file."); + isReady = true; - return 1; + InitLogger(DEBUG_LEVEL, CONSOLE_OUT, NULL); + + // Handle events + // + while (!quitting) { + if (GetNextEvent(eventMask, &event)) { + + if (dialog && IsDialogEvent(&event)) { + DialogSelect(&event, &dialog, &dlgItem); + } + else { + doEvent(event); + } + } + } + + + return EXIT_SUCCESS; +} + +///////////////////////////////////////////////////// +// Windows +// + +void +centerWindow(WindowPtr win) +{ + Rect screenRect; + Rect winRect; + int width, height, scrWidth, scrHeight; + int left, top; + + screenRect = screenBits.bounds; + winRect = win->portRect; + + scrWidth = screenRect.right - screenRect.left; + scrHeight = screenRect.bottom - screenRect.top; + + width = winRect.right - winRect.left; + height = winRect.bottom - winRect.top; + + left = (scrWidth / 2) - (width / 2); + top = (scrHeight / 2) - (height / 2); + + MoveWindow(win, left, top, FALSE); +} + +Rect +drawBtn(int h, int v, Str255 str) +{ + Rect r = { 0, 0, 15, 100 }; // top, left, bottom, right + + TextFont(monaco); + TextFace(0); + TextSize(9); + + OffsetRect(&r, h - 1, v - 1); + FrameRect(&r); + + OffsetRect(&r, -1, -1); + FillRect(&r, white); + FrameRect(&r); + + MoveTo(r.left + 4, r.bottom - 4); + + DrawString(str); + + return r; +} + +void +drawWindow(void) +{ + SetPort(mainWindow); + SetPortBits(&mainWindow->portBits); + + TextFont(helvetica); + TextFace(bold); + TextSize(18); + + MoveTo(20, 25); + DrawString("\pLogger test app"); + + TextFont(geneva); + TextFace(0); + TextSize(9); + + MoveTo(20, 50); + DrawString("\pThis is a simple app to test the Logger tool \ +to help debug your Macintosh application."); + + MoveTo(20, 65); + DrawString("\pThe Logger console should be visible at the bottom of the screen."); + + MoveTo(20, 90); + DrawString("\pThe following buttons highlight the different logging \ +functions of the logger."); + + printBtn = drawBtn(20, 110, "\plogger.print()"); + logBtn = drawBtn(20, printBtn.bottom + 10, "\plogger.log()"); + errBtn = drawBtn(20, logBtn.bottom + 10, "\plogger.error()"); + warnBtn = drawBtn(20, errBtn.bottom + 10, "\plogger.warn()"); + infoBtn = drawBtn(20, warnBtn.bottom + 10, "\plogger.info()"); + debugBtn = drawBtn(20, infoBtn.bottom + 10, "\plogger.debug()"); + + testBtn = drawBtn(20, debugBtn.bottom + 25, "\pRun tests"); +} + +void +showAbout(void) +{ + Handle versionRes; + Str255 versionStr = "\p"; + char appName[32]; + short item; + unsigned char* p; + + // Get version details from the version resource + // + versionRes = GetResource('vers', VERS_RES_ID); + if (versionRes) { + HLock(versionRes); + p = (unsigned char*)*versionRes; + + p += 6; // skip fixed header (6 bytes) + p += 1 + p[0]; // skip short version string + + // Get the long verstion string + // + BlockMove(p, versionStr, p[0] + 1); + + HUnlock(versionRes); + } + + strcpy(appName, APP_NAME); + appName[strlen(appName)] = '\0'; + + + ParamText(CtoPstr(appName), versionStr, "\p", "\p"); + + // Load the About dialog + // + dialog = GetNewDialog(ABOUT_DIALOG_ID, NULL, (WindowPtr)-1); + + if (!dialog) { + return; + } + + centerWindow(dialog); + + ShowWindow(dialog); + + do { + ModalDialog(NULL, &item); + } while (item != ABOUT_OK_BTN_ID); + + DisposeDialog(dialog); + dialog = NULL; +} + +///////////////////////////////////////////////////// +// Menu Bar +// + +void +loadMenuBar() +{ + Handle mbar = GetNewMBar(MBAR_ID); + SetMenuBar(mbar); + + appleMenu = GetMHandle(APPLE_MENU_ID); + AddResMenu(appleMenu, 'DRVR'); + + fileMenu = GetMHandle(FILE_MENU_ID); + + setMenuDefaults(); + DrawMenuBar(); +} + +void +setMenuDefaults() +{ + DisableItem(fileMenu, FILE_MENU_NEW_ID); +} + +short +onMenuClick(long menu_id) +{ + short menu, item; + short ret = true; + + menu = HiWord(menu_id); + item = LoWord(menu_id); + + if (isReady == 0) { + return false; + } + + switch (menu) { + case APPLE_MENU_ID: + switch (item) { + case APPLE_MENU_ABOUT_ID: { + showAbout(); + break; + } + default: { + Str255 da; + GrafPtr savePort; + + GetItem(appleMenu, item, da); + GetPort(&savePort); + OpenDeskAcc(da); + SetPort(savePort); + break; + } + } + break; + case FILE_MENU_ID: + switch(item) { + case FILE_MENU_NEW_ID: + break; + + case FILE_MENU_QUIT_ID: + quitting = true; + break; + } + break; + default: + ret = false; + } + + HiliteMenu(0); + return ret; +} + + +///////////////////////////////////////////////////// +// Events +// + +void +doEvent(EventRecord event) +{ + WindowPtr eventWin; + short eventIn; + char key; + Point localPt; + + if(logger.onEvent(event)) { + return; + } + + localPt = event.where; + GlobalToLocal(&localPt); + + switch (event.what) { + + case nullEvent: + break; + + case keyDown: + //case autoKey: + key = (char)(event.message & charCodeMask); + if ((event.modifiers & cmdKey) != 0) { + if (onMenuClick(MenuKey(key))) + break; + } + break; + + case keyUp: + key = (char)(event.message & charCodeMask); + break; + + case mouseDown: + eventIn = FindWindow(event.where, &eventWin); + + switch (eventIn) { + case inMenuBar: + onMenuClick(MenuSelect(event.where)); + break; + case inSysWindow: + SystemClick(&event, eventWin); + break; + case inContent: + if (PtInRect(localPt, &testBtn)) { + runTests(); + } + else if (PtInRect(localPt, &printBtn)) { + logger.print("Printing some text. It doesn't end with a new line. "); + } + else if (PtInRect(localPt, &logBtn)) { + logger.log("This is a simple log"); + } + else if (PtInRect(localPt, &errBtn)) { + logger.error("This is an error log"); + } + else if (PtInRect(localPt, &warnBtn)) { + logger.warn("This is a warning log"); + } + else if (PtInRect(localPt, &infoBtn)) { + logger.info("This is an info log"); + } + else if (PtInRect(localPt, &debugBtn)) { + logger.debug("This is a debug log"); + } + break; + case inDrag: + DragWindow(eventWin, event.where, &screenBits.bounds); + break; + case inGoAway: + if (TrackGoAway(eventWin, event.where)) { + quitting = true; + } + break; + } + break; + + case updateEvt: + + eventWin = (WindowPtr)event.message; + if (eventWin) { + BeginUpdate(eventWin); + + // We don't redraw on the first update + // + if(!isFirstUpdate) { + drawWindow(); + } + else { + isFirstUpdate = false; + } + + EndUpdate(eventWin); + } + break; + + case activateEvt: + break; + } } --- logger.c Tue Apr 8 14:38:34 2025 +++ logger.c Sun Mar 29 17:15:15 2026 @@ -16,74 +16,659 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * * SPDX-License-Identifier: GPL-3.0-or-later + * + * ---------------------------------------------------------------- + * Utility library to log to a file or to the console. + * + * Before using the logger, you must initialize it by calling: + * + * initLogger(DEBUG_LEVEL, LOG_METHOD, LOG_FILE); + * + * Where those variables are defined in logger.h. + * + * You may put all logging code inbetween the USE_LOGGER condition + * so that loggin logic can be easily fully disabled. + * + * #ifdef USE_LOGGER + * initLogger(DEBUG_LEVEL, LOG_METHOD, LOG_FILE); + * #endif + * + * #ifdef USE_LOGGER + * logger.debug("message"); + * #endif + * + * If you are logging to the console, you need to forward the + * application's events to the logger and ignore the event if it + * has been handled by the console: + * + * if(logger.onEvent(event)) { + * return; + * } + * ---------------------------------------------------------------- */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> +#include <string.h> #include "logger.h" + +void LogToNone(char *format, ...); +Boolean DoNotIntercept(EventRecord event); + +#ifdef USE_LOGGER + +typedef struct _LogTextState { + int font; + int size; + int mode; + int face; + +} LogTextState; + +#define CONSOLE_BAR_SIZE 12 +#define CONSOLE_TEXT_FONT monaco +#define CONSOLE_TEXT_SIZE 9 +#define CONSOLE_MIN_HEIGHT 67 +#define CONSOLE_MIN_WIDTH 150 + +LogTextState logSavedTextState; +PenState logSavedPentState; + +void UseLogger(LogLevel level, LogOutputMethod out, char *filePath); +void LogConsoleInit(void); +void LogConsoleDraw(void); +void LogConsoleUpdate(void); +void LogConsoleInitScrollbar(void); +void LogConsoleResetTE(void); +void LogConsoleResetScroll(void); +void LogConsoleScrollToBottom(void); +void LogConsoleSetLayout(); +void LogConsoleResize(Rect* newBounds); +void LogConsoleZoom(void); +void LogConsoleClear(void); +void LogToConsole(char *format, ...); +void LogToFile(char *format, ...); +void LogDefault(char *format, ...); +void LogDebug(char *format, ...); +void LogInfo(char *format, ...); +void LogWarn(char *format, ...); +void LogError(char *format, ...); +int DoLogGrow(Point p); +pascal void DoLogScroll(ControlHandle ctl, short ctlEventIn); +Boolean DoLogEvent(EventRecord event); + +void LogSaveTextState(void); +void LogRestoreTextState(void); + +#endif + Logger logger; void -init_logger(LogLevel level, - LogOutputMethod out, - char* file_path) +LogToNone(char* format, ...) { - logger.debug = log_debug; - logger.info = log_info; - logger.warn = log_warn; - logger.error = log_error; + (void) format; // unused. + return; +} - logger.log_level = level; - logger.output_method = out; - logger.file_path = file_path; +Boolean +DoNotIntercept(EventRecord event) +{ + return false; +} + +void +InitLogger(LogLevel level, + LogOutputMethod out, + char* filePath) +{ + logger.print = LogToNone; + logger.debug = LogToNone; + logger.info = LogToNone; + logger.warn = LogToNone; + logger.error = LogToNone; + logger.onEvent = DoNotIntercept; + + +#ifdef USE_LOGGER + UseLogger(level,out, filePath); +#endif +} + +#ifdef USE_LOGGER // Only load if required. + +void +UseLogger(LogLevel level, + LogOutputMethod out, + char* filePath) +{ + logger.log = LogDefault; + logger.debug = LogDebug; + logger.info = LogInfo; + logger.warn = LogWarn; + logger.error = LogError; + + logger.logLevel = level; + logger.outputMethod = out; + logger.filePath = filePath; + + logger.console.textArea = NULL; + if (out == CONSOLE_OUT) { - logger.log = log_to_console; + logger.onEvent = DoLogEvent; + LogConsoleInit(); } else if (out == FILE_OUT) { FILE* file; - logger.log = log_to_file; + // Using Unix New Line character for tail to + // read correctly on Linux. + // + logger.newLine = "\x0A\r"; + + logger.print = LogToFile; // Empty log file. If the file cannot be created, the log output // is set to NONE_OUT; // - file = fopen(logger.file_path, "w"); + file = fopen(logger.filePath, "w"); if(!file) { - logger.output_method = NONE_OUT; - logger.log = log_to_none; + logger.outputMethod = NONE_OUT; + logger.print = LogToNone; return; } fprintf(file, ""); fclose(file); } else { - logger.log = log_to_none; + logger.print = LogToNone; } } + +void +LogConsoleInit(void) +{ + GrafPtr oldPort; + Rect winBounds; + Rect viewRect; + Rect textRect; + + logger.console.lineHeight = CONSOLE_TEXT_SIZE + 2; + logger.console.width = screenBits.bounds.right - 2; + logger.console.scrollPos = 0; + logger.console.maxScrollPos = 0; + logger.console.nbLines = 0; + logger.console.contentHeight = 0; + logger.console.nbOutputLines = CONSOLE_LINES; + logger.console.height = (logger.console.lineHeight * CONSOLE_LINES) + + (CONSOLE_BAR_SIZE * 2) - 1; + + + // Make prevBounds to the zoomed in position for zoom toggle + // + logger.console.growBounds.left = screenBits.bounds.left + 5; + logger.console.growBounds.right = screenBits.bounds.right - 5; + logger.console.growBounds.top = screenBits.bounds.top + 25; + logger.console.growBounds.bottom = screenBits.bounds.bottom - 5; + + logger.console.prevBounds = logger.console.growBounds; + + logger.newLine = "\r"; + logger.print = LogToConsole; + + winBounds.left = screenBits.bounds.left + 1; + winBounds.right = screenBits.bounds.right - 1; + winBounds.bottom = screenBits.bounds.bottom - 1; + winBounds.top = winBounds.bottom - logger.console.height; + + + logger.console.window = NewWindow(0L, &winBounds, "\pConsole", TRUE, + plainDBox, (WindowPtr)-1L, TRUE, 0); + + if (!logger.console.window) { + logger.print = LogToNone; + return; + } + + logger.console.origBounds = winBounds; + + GetPort(&oldPort); + SetPort(logger.console.window); + + SendBehind(logger.console.window, NULL); + + LogConsoleSetLayout(); + + + // Tex Edit + // + viewRect = logger.console.view; + //InsetRect(&viewRect, 2, 2); + + textRect = viewRect; + InsetRect(&textRect, 2, 0); + + logger.console.textArea = TENew(&textRect, &viewRect); + + (*(logger.console.textArea))->txFont = CONSOLE_TEXT_FONT; + (*(logger.console.textArea))->txSize = CONSOLE_TEXT_SIZE; + (*(logger.console.textArea))->lineHeight = logger.console.lineHeight; + (*(logger.console.textArea))->fontAscent = logger.console.lineHeight - 2; + (*(logger.console.textArea))->txFace = 0; + + //TEActivate(logger.console.textArea); + + LogConsoleInitScrollbar(); + + LogConsoleDraw(); + + SetPort(oldPort); +} + +void LogConsoleDraw(void) +{ + Rect r; + Pattern tbarPattern; + GrafPtr oldPort; + + GetPort(&oldPort); + SetPort(logger.console.window); + + LogSaveTextState(); + GetPenState(&logSavedPentState); + PenNormal(); + + EraseRect(&logger.console.view); + + GetIndPattern(&tbarPattern, sysPatListID, 24); + FillRect(&logger.console.titleBar, tbarPattern); + FrameRect(&logger.console.titleBar); + + FillRect(&logger.console.bottomBar, white); + FrameRect(&logger.console.bottomBar); + + FillRect(&logger.console.growBtn, white); + FrameRect(&logger.console.growBtn); + r = logger.console.growBtn; + InsetRect(&r, 5, 3); + OffsetRect(&r, 1, 1); + FrameRect(&r); + + r.right -= 1; + r.bottom -= 1; + OffsetRect(&r, -2, -2); + FillRect(&r, white); + FrameRect(&r); + + // Lower button + // + FillRect(&logger.console.lowerBtn, white); + FrameRect(&logger.console.lowerBtn); + MoveTo(logger.console.lowerBtn.left + 4, + logger.console.lowerBtn.bottom - 2); + + TextFont(geneva); + TextFace(0); + TextSize(12); + + DrawString("\p_"); + + + // Clear button + // + FillRect(&logger.console.clearBtn, white); + FrameRect(&logger.console.clearBtn); + MoveTo(logger.console.clearBtn.left + 4, + logger.console.clearBtn.bottom - 2); + + TextFont(geneva); + TextFace(0); + TextSize(8); + + DrawString("\pclear"); + + // Close button + // + FillRect(&logger.console.closeBtn, white); + FrameRect(&logger.console.closeBtn); + + // Expand button + // + FillRect(&logger.console.zoomBtn, white); + FrameRect(&logger.console.zoomBtn); + + + LogConsoleUpdate(); + + + LogRestoreTextState(); + SetPenState(&logSavedPentState); + + SetPort(oldPort); +} + +void LogConsoleUpdate(void) +{ + TEUpdate(&logger.console.view, logger.console.textArea); + DrawControls(logger.console.window); +} + void -log_to_none(char* format, ...) +LogConsoleInitScrollbar(void) { - return; + GrafPtr oldPort; + Rect scrollbarRect; + int value; + + GetPort(&oldPort); + SetPort(logger.console.window); + + scrollbarRect.left = logger.console.window->portRect.right - 15; + scrollbarRect.right = logger.console.window->portRect.right + 1; + scrollbarRect.top = logger.console.titleBar.bottom - 1; + scrollbarRect.bottom = logger.console.bottomBar.top + 1; + + value = logger.console.contentHeight - logger.console.height; + + logger.console.vScroll = NewControl(logger.console.window, + &scrollbarRect, + "\p", + TRUE, + 0, // intinial value; + 0, // min + value, // max + scrollBarProc, + 0); + + SetCtlMax(logger.console.vScroll, + logger.console.contentHeight - logger.console.height); + + SetCtlValue(logger.console.vScroll, logger.console.scrollPos); + + HiliteControl(logger.console.vScroll, 255); + + SetPort(oldPort); } void -log_to_console(char* format, ...) +LogConsoleResetTE(void) { + Rect viewRect = logger.console.view; + + //InsetRect(&viewRect, 2, 2); + + // Resize the size of the text area + // + (*(logger.console.textArea))->viewRect = viewRect; + (*(logger.console.textArea))->destRect.right = viewRect.right - 1; +} + +void +LogConsoleResetScroll(void) +{ + int oldMaxScrollPos = logger.console.maxScrollPos; + + logger.console.maxScrollPos = (logger.console.nbLines + - logger.console.nbOutputLines) + * logger.console.lineHeight; + + if (logger.console.maxScrollPos <= 0) { + + // If there is nothing to scroll, + // put everything back at the top + // + TEScroll(0, logger.console.scrollPos, logger.console.textArea); + + logger.console.scrollPos = 0; + logger.console.maxScrollPos = 0; + } + else if (logger.console.scrollPos > logger.console.maxScrollPos) { + + // Window is growing + // + if (logger.console.maxScrollPos < oldMaxScrollPos) { + + int delta = logger.console.scrollPos - logger.console.maxScrollPos; + TEScroll(0, delta, logger.console.textArea); + } + + logger.console.scrollPos = logger.console.maxScrollPos; + } + else { + HiliteControl(logger.console.vScroll, 0); + } + + MoveControl(logger.console.vScroll, + logger.console.bounds.right - 15, + logger.console.titleBar.bottom - 1); + + SizeControl(logger.console.vScroll, + 16, + logger.console.bottomBar.top + - logger.console.titleBar.bottom + 2); + + SetCtlMax(logger.console.vScroll, logger.console.maxScrollPos); + SetCtlValue(logger.console.vScroll, logger.console.scrollPos); +} + +void +LogConsoleScrollToBottom(void) +{ + int delta = logger.console.maxScrollPos - logger.console.scrollPos; + + logger.console.scrollPos = logger.console.maxScrollPos; + SetCtlValue(logger.console.vScroll, logger.console.scrollPos); + + TEScroll(0, -delta, logger.console.textArea); + + LogConsoleUpdate(); +} + +void +LogConsoleSetLayout(void) +{ + logger.console.bounds = logger.console.window->portRect; + + logger.console.view = logger.console.bounds; + logger.console.view.right -= 15; + logger.console.view.top += CONSOLE_BAR_SIZE; + logger.console.view.bottom -= CONSOLE_BAR_SIZE - 1; + + // Title bar & buttons + // + logger.console.titleBar.left = logger.console.bounds.left - 1; + logger.console.titleBar.right = logger.console.bounds.right + 1; + logger.console.titleBar.top = logger.console.bounds.top - 1; + logger.console.titleBar.bottom = logger.console.titleBar.top + + CONSOLE_BAR_SIZE + 1; + + logger.console.bottomBar.left = logger.console.bounds.left - 1; + logger.console.bottomBar.right = logger.console.bounds.right + 1; + logger.console.bottomBar.top = logger.console.bounds.bottom + - CONSOLE_BAR_SIZE +1; + logger.console.bottomBar.bottom = logger.console.bounds.bottom + 1; + + + logger.console.zoomBtn.right = logger.console.bounds.right - 5; + logger.console.zoomBtn.left = logger.console.zoomBtn.right - 9; + logger.console.zoomBtn.top = logger.console.bounds.top + 1; + logger.console.zoomBtn.bottom = logger.console.zoomBtn.top + 9; + + logger.console.growBtn.right = logger.console.bounds.right + 1; + logger.console.growBtn.left = logger.console.growBtn.right - 16; + logger.console.growBtn.bottom = logger.console.bounds.bottom + 1; + logger.console.growBtn.top = logger.console.growBtn.bottom + - CONSOLE_BAR_SIZE; + + + logger.console.lowerBtn.right = logger.console.zoomBtn.left - 10; + logger.console.lowerBtn.left = logger.console.lowerBtn.right - 16; + logger.console.lowerBtn.top = logger.console.bounds.top + 1; + logger.console.lowerBtn.bottom = logger.console.lowerBtn.top + 9; + + logger.console.clearBtn.right = logger.console.lowerBtn.left -4; + logger.console.clearBtn.left = logger.console.clearBtn.right - 26; + logger.console.clearBtn.top = logger.console.bounds.top + 1; + logger.console.clearBtn.bottom = logger.console.clearBtn.top + 9; + + logger.console.closeBtn.left = logger.console.bounds.left + 5; + logger.console.closeBtn.right = logger.console.closeBtn.left + 9; + logger.console.closeBtn.top = logger.console.bounds.top + 1; + logger.console.closeBtn.bottom = logger.console.closeBtn.top + 9; +} + +void +LogConsoleResize(Rect* newBounds) +{ + int width = newBounds->right - newBounds->left; + int height = newBounds->bottom - newBounds->top; + + int nbLines = (int)((height - (CONSOLE_BAR_SIZE * 2) + 1) + / logger.console.lineHeight); + + // Adjust the hieight of the window to match the number of + // lines and line height of the TE + // + logger.console.nbOutputLines = nbLines; + logger.console.width = width; + logger.console.height = (logger.console.lineHeight * nbLines) + + (CONSOLE_BAR_SIZE * 2) - 1; + + newBounds->bottom = newBounds->top + logger.console.height; + + MoveWindow(logger.console.window, + newBounds->left, + newBounds->top, + TRUE); + + SizeWindow(logger.console.window, + logger.console.width, + logger.console.height, + FALSE); + + EraseRect(newBounds); + LogConsoleSetLayout(); + LogConsoleResetTE(); + LogConsoleResetScroll(); + LogConsoleDraw(); + + //LogConsoleScrollToBottom(); +} + +void +LogConsoleZoom(void) +{ + Point topLeft; + Point bottomRight; + + topLeft.h = logger.console.window->portRect.left; + topLeft.v = logger.console.window->portRect.top; + + bottomRight.h = logger.console.window->portRect.right; + bottomRight.v = logger.console.window->portRect.bottom; + + LocalToGlobal(&topLeft); + LocalToGlobal(&bottomRight); + + LogConsoleResize(&logger.console.prevBounds); + + logger.console.prevBounds.left = topLeft.h; + logger.console.prevBounds.top = topLeft.v; + logger.console.prevBounds.right = bottomRight.h; + logger.console.prevBounds.bottom = bottomRight.v; +} + +void +LogConsoleClear(void) +{ + GrafPtr oldPort; + Rect scrollbarRect; + int value; + + GetPort(&oldPort); + SetPort(logger.console.window); + + // Move back to the top of the text area + // + TEScroll(0, logger.console.scrollPos, logger.console.textArea); + + // Select all and erase + // + TESetSelect(0,32767,logger.console.textArea); + TEDelete(logger.console.textArea); + + logger.console.scrollPos = 0; + logger.console.maxScrollPos = 0; + logger.console.nbLines = 0; + logger.console.contentHeight = 0; + + SetCtlMax(logger.console.vScroll, 0); + SetCtlValue(logger.console.vScroll, 0); + + HiliteControl(logger.console.vScroll, 255); + + LogConsoleUpdate(); + + SetPort(oldPort); +} + +void +LogToConsole(char* format, ...) +{ va_list ap; + char log[256] = ""; + int oldMaxScroll = logger.console.maxScrollPos; + GrafPtr oldPort; + + GetPort(&oldPort); + SetPort(logger.console.window); + va_start(ap, format); - vprintf(format, ap); + vsprintf(log, format, ap); va_end(ap); - printf("\n"); + + TEInsert(log, strlen(log), logger.console.textArea); + + + // Update scroll data + // + logger.console.nbLines = (*(logger.console.textArea))->nLines; + logger.console.contentHeight = logger.console.nbLines * logger.console.lineHeight; + + logger.console.maxScrollPos = (logger.console.nbLines + - logger.console.nbOutputLines) + * logger.console.lineHeight; + + SetCtlMax(logger.console.vScroll, logger.console.maxScrollPos); + + + // Auto-scroll to bottom (do this only once) + // + if (logger.console.nbLines == logger.console.nbOutputLines + 1) { + // Enable the scrollbar once it is needed + // + HiliteControl(logger.console.vScroll, 0); + } + + if (logger.console.nbLines > logger.console.nbOutputLines && + logger.console.scrollPos == oldMaxScroll) { + + LogConsoleScrollToBottom(); + } + + SetPort(oldPort); + + ShowHide(logger.console.window, TRUE); } void -log_to_file(char* format, ...) +LogToFile(char* format, ...) { FILE* file; va_list ap; @@ -91,7 +676,7 @@ log_to_file(char* format, ...) // Open file in Append Binary mode so the // new line character is not converted. // - file = fopen(logger.file_path, "ab"); + file = fopen(logger.filePath, "ab"); if(!file) { return; } @@ -100,21 +685,29 @@ log_to_file(char* format, ...) vfprintf(file, format, ap); va_end(ap); - // Using Unix New Line character for tail to - // read correctly on Linux. - // - fprintf(file, " \x0A\r"); - fclose(file); } void -log_debug(char* format, ...) +LogDefault(char* format, ...) { va_list ap; char log[256] = ""; + + va_start(ap, format); + vsprintf(log, format, ap); + va_end(ap); - if (logger.log_level < DEBUG_LEVEL) { + logger.print("%s %s", log, logger.newLine); +} + +void +LogDebug(char* format, ...) +{ + va_list ap; + char log[256] = ""; + + if (logger.logLevel < DEBUG_LEVEL) { return; } @@ -122,16 +715,17 @@ log_debug(char* format, ...) vsprintf(log, format, ap); va_end(ap); - logger.log("DEBUG: %s", log); + + logger.print("DEBUG: %s %s", log, logger.newLine); } void -log_info(char* format, ...) +LogInfo(char* format, ...) { va_list ap; char log[256] = ""; - if (logger.log_level < INFO_LEVEL) { + if (logger.logLevel < INFO_LEVEL) { return; } @@ -139,16 +733,16 @@ log_info(char* format, ...) vsprintf(log, format, ap); va_end(ap); - logger.log("INFO: %s", log); + logger.print("INFO: %s %s", log, logger.newLine); } void -log_warn(char* format, ...) +LogWarn(char* format, ...) { va_list ap; char log[256] = ""; - if (logger.log_level < WARNING_LEVEL) { + if (logger.logLevel < WARNING_LEVEL) { return; } @@ -156,16 +750,16 @@ log_warn(char* format, ...) vsprintf(log, format, ap); va_end(ap); - logger.log("WARNING: %s", log); + logger.print("WARNING: %s %s", log, logger.newLine); } void -log_error(char* format, ...) +LogError(char* format, ...) { va_list ap; char log[256] = ""; - if (logger.log_level < ERROR_LEVEL) { + if (logger.logLevel < ERROR_LEVEL) { return; } @@ -173,5 +767,236 @@ log_error(char* format, ...) vsprintf(log, format, ap); va_end(ap); - logger.log("ERROR: %s", log); + logger.print("ERROR: %s %s", log, logger.newLine); } + + +int +DoLogGrow(Point p) +{ + Rect newBounds; + GrafPtr savePort; + Rect limits; + long theResult; + Point orig = {0, 0}; + + GetPort(&savePort); + SetPort(logger.console.window); + + SetRect(&limits, CONSOLE_MIN_WIDTH, // Min horizontal size + CONSOLE_MIN_HEIGHT, // Min vertical size + screenBits.bounds.right, // Max horizontal size + screenBits.bounds.bottom); // Max vertical size + + theResult = GrowWindow(logger.console.window, p, &limits); + if (theResult == 0) { + return 0; + } + + LocalToGlobal(&orig); + newBounds.left = orig.h; + newBounds.right = newBounds.left + LoWord(theResult); + newBounds.top = orig.v; + newBounds.bottom = newBounds.top + HiWord(theResult); + + LogConsoleResize(&newBounds); + + logger.console.prevBounds = logger.console.growBounds; + + return 1; +} + +pascal void DoLogScroll(ControlHandle ctl, short ctlEventIn) +{ + int value = logger.console.scrollPos; + int orig = value; + + (void) ctl; // unused + + switch (ctlEventIn) { + + case inUpButton: + value -= logger.console.lineHeight; + break; + + case inDownButton: + value += logger.console.lineHeight; + break; + + case inPageUp: + value -= logger.console.lineHeight * (logger.console.nbOutputLines - 1); + break; + + case inPageDown: + value += logger.console.lineHeight * (logger.console.nbOutputLines - 1); + break; + } + + // Clamp + // + if (value < 0) { + value = 0; + } + else if (value > logger.console.maxScrollPos) { + value = logger.console.maxScrollPos; + } + + if (value != orig) { + logger.console.scrollPos = value; + SetCtlValue(logger.console.vScroll, logger.console.scrollPos); + + TEScroll(0, -(value - orig), logger.console.textArea); + + LogConsoleUpdate(); + } +} + +Boolean DoLogEvent(EventRecord event) +{ + Boolean interceped = FALSE; + + GrafPtr oldPort; + WindowPtr eventWin; + ControlHandle ctl; + int eventIn, ctlEventIn; + Point localPt; + + if (logger.outputMethod != CONSOLE_OUT) { + return FALSE; + } + + GetPort(&oldPort); + SetPort(logger.console.window); + + localPt = event.where; + GlobalToLocal(&localPt); + + + switch (event.what) { + + case nullEvent: + break; + + case mouseDown: + eventIn = FindWindow(event.where, &eventWin); + + if (eventWin != logger.console.window) { + interceped = FALSE; + break; + } + + SelectWindow(eventWin); + + switch (eventIn) { + + case inContent: + + ctlEventIn = FindControl(localPt, eventWin, &ctl); + + if (PtInRect(localPt, &logger.console.closeBtn)) { + HideWindow(logger.console.window); + interceped = TRUE; + } + else if (PtInRect(localPt, &logger.console.lowerBtn)) { + Rect origBounds = logger.console.origBounds; + + logger.console.prevBounds = logger.console.growBounds; + LogConsoleResize(&origBounds); + LogConsoleScrollToBottom(); + } + else if (PtInRect(localPt, &logger.console.clearBtn)) { + LogConsoleClear(); + } + else if (PtInRect(localPt, &logger.console.zoomBtn)) { + LogConsoleZoom(); + LogConsoleScrollToBottom(); + } + else if (PtInRect(localPt, &logger.console.titleBar)) { + DragWindow(eventWin, event.where, &screenBits.bounds); + InvalRect(&screenBits.bounds); + } + else if (PtInRect(localPt, &logger.console.growBtn)) { + DoLogGrow(event.where); + } + else if (ctl == logger.console.vScroll) { + + if (ctlEventIn == inThumb) { + int ctlValue; + int delta; + + // Dragging the thumb + // + TrackControl(ctl, localPt, NULL); + + ctlValue = GetCtlValue(ctl); + delta = ctlValue - logger.console.scrollPos; + + logger.console.scrollPos = ctlValue; + + TEScroll(0, -delta, logger.console.textArea); + + LogConsoleUpdate(); + + } + else { + // Buttons + page scroll + // + TrackControl(ctl, localPt, DoLogScroll); + } + } + + interceped = TRUE; + break; + case inGrow: + logger.debug("In grow"); + interceped = TRUE; + break; + case inGoAway: + if (TrackGoAway(eventWin, event.where)) { + HideWindow(logger.console.window); + interceped = TRUE; + } + break; + } + break; + + case updateEvt: + eventWin = (WindowPtr)event.message; + + if (eventWin && eventWin == logger.console.window) { + BeginUpdate(eventWin); + + LogConsoleDraw(); + + EndUpdate(eventWin); + } + break; + + case activateEvt: + break; + } + + SetPort(oldPort); + + return interceped; +} + +void +LogSaveTextState(void) +{ + logSavedTextState.font = thePort->txFont; + logSavedTextState.size = thePort->txSize; + logSavedTextState.mode = thePort->txMode; + logSavedTextState.face = thePort->txFace; +} + +void +LogRestoreTextState(void) +{ + TextFont(logSavedTextState.font); + TextSize(logSavedTextState.size); + TextMode(logSavedTextState.mode); + TextFace(logSavedTextState.face); +} + +#endif --- logger.h Tue Apr 8 09:30:11 2025 +++ logger.h Sat Mar 28 18:49:11 2026 @@ -1,6 +1,6 @@ /* logger.h * - * Copyright 2025 Francois Techene + * Copyright 2026 Francois Techene * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,19 @@ #ifndef LOGGER_H #define LOGGER_H +//////////////////////////////////////////////// +// Logger setup +// +#define USE_LOGGER // Comment to disable logs + +#define LOG_LEVEL DEBUG_LEVEL // NONE_LEVEL, ERROR_LEVEL, WARNING_LEVEL, INFO_LEVEL, DEBUG_LEVEL +#define LOG_METHOD CONSOLE_OUT // NONE_OUT, CONSOLE_OUT, FILE_OUT +#define LOG_FILE "::logs:output.log" // Change to the path to your log file + // requires LOG_METHOD set to FILE_OUT +#define CONSOLE_LINES 4 + +//////////////////////////////////////////////// + typedef enum { NONE_LEVEL = 0, ERROR_LEVEL, @@ -37,36 +50,70 @@ typedef enum { } LogOutputMethod; +#ifdef USE_LOGGER // Only load if required. +typedef struct _LogConsole { + + WindowPtr window; + TEHandle textArea; + + ControlHandle vScroll; + + Rect bounds; + Rect origBounds; + Rect growBounds; + Rect prevBounds; + Rect titleBar; + Rect bottomBar; + Rect view; + + Rect lowerBtn; + Rect clearBtn; + Rect closeBtn; + Rect zoomBtn; + Rect growBtn; + + int width; + int height; + int lineHeight; + int contentHeight; + int nbLines; + int scrollPos; + int maxScrollPos; + int nbOutputLines; + +} LogConsole; + +#endif + typedef struct _Logger { + void (*print) (char* format, ...); void (*log) (char* format, ...); void (*debug) (char* format, ...); void (*info) (char* format, ...); void (*warn) (char* format, ...); void (*error) (char* format, ...); + Boolean (*onEvent) (EventRecord event); - char* file_path; - int output_method; - int log_level; +#ifdef USE_LOGGER // Only load if required. + LogConsole console; + char* filePath; + int outputMethod; + int logLevel; + char* newLine; +#endif + } Logger; extern Logger logger; -void init_logger(LogLevel level, - LogOutputMethod out, - char* file_path); +void InitLogger(LogLevel level, + LogOutputMethod out, + char* file_path); -void log_debug(char* format, ...); -void log_info(char* format, ...); -void log_warn(char* format, ...); -void log_error(char* format, ...); - -void log_to_none(char* format, ...); -void log_to_console(char* format, ...); -void log_to_file(char* format, ...); - +Boolean DoLogEvent(EventRecord event); #endif --- README Tue Apr 8 14:39:25 2025 +++ README Sun Mar 29 17:18:36 2026 @@ -9,7 +9,8 @@ This library has been built and tested using THINK C v In order to install it, just import `logger.c` and `logger.h` to your project and include `logger.h` in your source files. -The Logger library requires ANSI to be added to your project. +The Logger library requires ANSI to be added to your project. It also requires the MacTraps libs if you plan to use the graphical console. +You may only use the graphical cosole if you are developing a graphical application and that all the Toolbox inits are made by your application. --- @@ -17,35 +18,36 @@ The Logger library requires ANSI to be added to your p ## Initializing the logger: -For the logger to log to the console (through printf()), call the init_logger() function with a log level and `CONSOLE_OUT` as the output method. +The logger can log to a graphical console that pops up at the bottom of the screen. +If you want to use the console, call the InitLogger() function with a log level and `CONSOLE_OUT` as the output method. - init_logger(ERROR_LEVEL, - CONSOLE_OUT, - NULL); + InitLogger(DEBUG_LEVEL, + CONSOLE_OUT, + NULL); -For the logger to log to a file, call the init_logger() function with the FILE_OUT as the output method along with the path to the file to log to. +For the logger to log to a file, call the InitLogger() function with the FILE_OUT as the output method along with the path to the file to log to. - init_logger(DEBUG_LEVEL, - FILE_OUT, - "file.log"); + InitLogger(DEBUG_LEVEL, + FILE_OUT, + ":file.log"); You can define paths as follow: -* "file.log" - A simple file name relative to the project. -* ":logs:file.log" - An path relative to the project. +* ":file.log" - A simple file name relative to the project. +* "::logs:file.log" - Going back up 1 level and moving to the logs folder. * "Macintosh HD:logs:file.log" - An absolute path. Tip: The log file can be stored on a network volume (from Netatalk) and loaded with `tail -f` on a modern machine so that logs are shown in real time. Note that if the log file doesn't exist, it is created but only if its containg folder exists. -If the file cannot be created, the output method is set to NONE_OUT. -The log file is cleared every time init_logger() is called. +If the file cannot be created, the output method is set to NONE_OUT. Nothing gets logged. +The log file is cleared every time InitLogger() is called. Log output methods are as follow: - NONE_OUT = 0, - CONSOLE_OUT = 1, - FILE_OUT = 2, + NONE_OUT = 0, // Nothing gets logged. Fail safe internal use. + CONSOLE_OUT = 1, // Log to the graphical console. + FILE_OUT = 2 // Log to a file. Log levels are as follow: NONE_LEVEL = 0, @@ -55,14 +57,14 @@ Log levels are as follow: DEBUG_LEVEL = 4, -init_logger() initializes the global variable `logger`. +InitLogger() initializes the global variable `logger`. ## Using the logger: -You can log a new line by calling: +You can log some text by calling: - logger.log(char* format, ...); + logger.print(char* format, ...); You can format your log message with variables: @@ -74,22 +76,34 @@ You can format your log message with variables: You can also use the following methods to differentiate debug, warning and error messages from your log output. + logger.log(char* format, ...); // Same as print() but with a trailing new line. logger.debug(char* format, ...); logger.info(char* format, ...); logger.warn(char* format, ...); logger.error(char* format, ...); + + +## Using the console: + +If you are logging to the console, you need to forward the application's events to the logger and ignore the event if it has been handled by the console. + +You may place the following code at the top of your events handler function: + + if(logger.onEvent(event)) { + return; + } ## Customizing: -You can override the logger.log() function to handle logs the way you want, like printing the message in a text area on the screen. +You can override the logger.print() function to handle logs the way you want, like for defining your own output method. - void my_log(char* format, ...) + void myLog(char* format, ...) { //Handle my log message. } -After initializing with no output, just set logger.log. +After initializing with no output, just set logger.print. - init_logger(DEBUG_LEVEL, NONE_OUT, NULL); - logger.log = my_log; + InitLogger(DEBUG_LEVEL, NONE_OUT, NULL); + logger.print = myLog; --- test.log Tue Apr 8 14:40:16 2025 +++ test.log Fri Mar 27 11:40:52 2026 @@ -21,23 +21,3 @@ \____/\___/ \__, |\__, |\___|_| |___/ |___/ - -#### Testing NONE_LEVEL: Must not show any log: - -#### Testing ERROR_LEVEL: -ERROR: This error log should be visible - -#### Testing WARNING_LEVEL: -WARNING: This warning log should be visible -ERROR: This error log should be visible - -#### Testing INFO_LEVEL: -INFO: This info log should be visible -WARNING: This warning log should be visible -ERROR: This error log should be visible - -#### Testing DEBUG_LEVEL: -DEBUG: This is a debug message with number 1 -INFO: This is an info message with number 2 -WARNING: This is a warning with number 3 -ERROR: This is an error with number 4