/* * MAINWIN.C vi:ts=4 * * Copyright (c) Eddy Carroll, September 1994. * * This module handles the main SnoopDos window, outputting new events * to the window, menu options, etc. */ #include "system.h" #include "snoopdos.h" #include "gui.h" #if 0 #define DB(s) KPrintF(s) #else #define DB(s) #endif /* * In earlier versions, we used to lock layers whenever we rendered * event output to the SnoopDos main window buffer. This avoided * deadlocks with input.device. Now we use a different work around, * but for the sake of documentation, we leave the original calls * in (you never know when they might come in handy) * * (The different workaround is to never monitor tasks that have * locked layers on the SnoopDos screen/window). */ #if 0 #define LOCK_LAYERS \ (LockLayerInfo(&MainWindow->WScreen->LayerInfo), \ LockLayer(0, MainWindow->RPort->Layer)) #define UNLOCK_LAYERS \ (UnlockLayer(MainWindow->RPort->Layer), \ UnlockLayerInfo(&MainWindow->WScreen->LayerInfo)) #else #define LOCK_LAYERS #define UNLOCK_LAYERS #endif /* * Interface to RawPutChar() in exec.library * (It takes a single parameter in D0) */ #pragma libcall SysBase RawPutChar 204 001 /* * These two are imported from SNOOPDOS.C */ extern char Version[]; extern char SnoopDosTitle[]; extern char SnoopDosTitleKey[]; /* * Global vars used by the main window */ struct Gadget *MainGadList; /* Main gadget list */ struct Menu *MainWinMenu; /* Main window menu */ struct TextFont *BufferFont; /* Handle for buffer font */ struct TextAttr *CurMainGadgetFA; /* Current gadget font attr */ struct TextAttr *CurMainBufferFA; /* Current buffer font attr */ struct RastPort InvertRP; /* Used when sizing columns */ int NumBufLines; /* Current number of lines in buffer */ int ChangedSpacing; /* 1 = edited spacing since last redraw */ int ScrollDirection; /* ID of current scroll gadget, or 0 */ int ScrollCount; /* Used for pacing INTUITICKS in scroll */ int RemovedGadgets; /* True if gadgets removed from window */ int AwaitingResize; /* True if waiting for window resize */ #define RESIZE_DONE 0 /* Resize is now complete */ #define RESIZE_MIDDLE 1 /* Resize happened in middle of buffer */ #define RESIZE_BOTTOM 2 /* Resize happened at end of buffer */ #define MOVECOL_FREE 0 /* Reposition column freely */ #define MOVECOL_FIXED 1 /* Reposition column fixed wrt RHS col */ #define INVERT_HEADER 0 /* Invert only the header part of col */ #define INVERT_FULLBOX 1 /* Invert entire column */ int BoxLeft; /* Pixel offset of left edge of box */ int BoxTop; /* Pixel offset of top edge of box */ int BoxWidth; /* Width of box in pixels */ int BoxHeight; /* Height of box in pixels */ int BoxHeaderLine; /* Baseline for box header text */ int BoxInLeft; /* Pixel offset of inside top of box */ int BoxInTop; /* Pixel offset of inside left of box */ int BoxInWidth; /* Inside width of box */ int BoxInHeight; /* Inside height of box */ int BoxCharWidth; /* Width of a single character in box */ int BoxCharHeight; /* Height of a single character in box */ int BoxCols; /* Number of columns of text in box */ int BoxRows; /* Number of rows of text in box */ int BoxSpacing; /* Height between font baselines in box */ int BoxLowest; /* Bottom point of box scroll arrow */ int BoxBaseline; /* Baseline of text line 0 in box */ int VBorderGap; /* Aspect-adjusted space betw hrz lines */ int LogButtonLeft; /* Left (X) Position of Logfile button */ int LogButtonID; /* Gadget ID of log button on view */ int LeftCol; /* Leftmost displayed column */ int RightCol; /* Rightmost displayed column */ int DraggingRow; /* 1 if dragging a row with mouse */ int SelectRow; /* Row to draw highlighted */ int DraggingColumn; /* 1 if dragging a column with mouse */ int ClickStartCol; /* Col pos where mouse first clicked */ int ClickCurCol; /* Current column where mouse clicked */ int SelectStartCol; /* Start position of selected column */ int DragOrigColWidth; /* Starting width of widening column */ int NextOrigColWidth; /* Starting width of following column */ int OrigLeftCol; /* Starting position of left column */ int OrigBufWidth; /* Starting width of buffer */ int MovedColumn; /* True if column position was altered */ EventFormat *DragEvent; /* Points to column being widened */ EventFormat *SelectEvent; /* Points to column being highlighted */ BPTR LogFile; /* Handle of output log file */ UBYTE *LogBuffer; /* Buffer for buffered i/o (or NULL) */ ULONG LogBufferSize; /* Size of logfile output buffer */ ULONG LogBufferPos; /* Current position in output buffer */ UBYTE MainKeyboard[KB_SHORTCUT_SIZE]; /* For keyboard equivs */ /* * This table is used to select the appropriate row qualifier * for the row selection mechanism */ UWORD RowQualTable[] = { 0, /* ROWQUAL_ANY -- ignore qualifiers */ 0, /* ROWQUAL_NONE -- don't allow any qualifiers */ IE_SHIFT, /* ROWQUAL_SHIFT -- only select if SHIFT is pressed */ IE_ALT, /* ROWQUAL_ALT -- only select if ALT is pressed */ IE_CTRL, /* ROWQUAL_CTRL -- only select if CTRL is pressed */ IE_ALL /* ROWQUAL_ALL -- only select if any qual pressed */ }; /* * Now our font structures. * * We do everything in terms of FontAttr structures, and whenever a * window needs a font, it OpenFont's it itself. This has several * advantages: it allows each window to dynamically select the * best-fitting font, and it also ensures no problems if we have a * window open with one font and the user than changes the font * to something else -- the first window will still have a valid * lock on the old font. */ char BufferLine[MAX_BUFFER_WIDTH]; /* Storage area for buffer lines */ struct TextAttr TopazFontAttr = {"topaz.font", 8, FS_NORMAL, FPB_ROMFONT }; struct TextAttr SystemFontAttr = {SystemFontName, 0, FS_NORMAL, FPB_DISKFONT}; struct TextAttr WindowFontAttr = {WindowFontName, 0, FS_NORMAL, FPB_DISKFONT}; struct TextAttr BufferFontAttr = {BufferFontName, 0, FS_NORMAL, FPB_DISKFONT}; struct { struct TextAttr *gadgetfa; struct TextAttr *bufferfa; } MainWindowFontList[] = { &WindowFontAttr, &BufferFontAttr, &SystemFontAttr, &BufferFontAttr, &WindowFontAttr, &SystemFontAttr, &SystemFontAttr, &SystemFontAttr, &TopazFontAttr, &SystemFontAttr, &TopazFontAttr, &TopazFontAttr, NULL, NULL }; /* * This is used by the boopsi proportional gadgets */ static struct TagItem RZ_MapTags[] = { PGA_Top, ICSPECIAL_CODE, TAG_END }; /* * This holds the image pointers for our BOOPSI scroll gadgets */ struct Image *ScrollImage[4]; /* * This structure is used to define our four scroll gadgets. We * define the relative positions of each gadget in terms of the * formula (Scale * Width + offset) or (Scale * Height + offset) * where Width and Height are taken from the image structure. */ struct ScrollData { int gadgetid; /* ID of the gadget */ int imagetype; /* Which type of image we're creating */ LONG widthscale; /* Scale applied to width */ LONG widthoffset; /* Offset applied to width */ LONG heightscale; /* Scale applied to height */ LONG heightoffset; /* Offset applied to offset */ LONG borderpos; /* Tag for border containing gadget */ } ScrollArrows[] = { GID_LEFTARROW, LEFTIMAGE, -2, -17, -1, 1, GA_BottomBorder, GID_RIGHTARROW, RIGHTIMAGE, -1, -17, -1, 1, GA_BottomBorder, GID_UPARROW, UPIMAGE, -1, 1, -2, -9, GA_RightBorder, GID_DOWNARROW, DOWNIMAGE, -1, 1, -1, -9, GA_RightBorder }; /* * Now the gadgets for the main window */ struct MainGadgets { UWORD gadgid; /* Gadget ID */ UWORD stringid; /* ID of label name */ UBYTE col4; /* Column in 4 x 2 layout */ UBYTE row4; /* Row in 4 x 2 layout */ UBYTE col6; /* Column in 4 x 2 layout */ UBYTE row6; /* Row in 4 x 2 layout */ UBYTE col8; /* Column in 4 x 2 layout */ UBYTE col12; /* Column in 12 x 1 layout */ UBYTE widthtype; /* 0=narrow, 1=wide, 2=status */ }; struct MainGadgets MainGadgs[] = { GID_HIDE, MSG_HIDE_GAD, 0, 2, 0, 1, 0, 4, MAIN_NARROW, GID_QUIT, MSG_QUIT_GAD, 1, 2, 1, 1, 1, 5, MAIN_NARROW, GID_PAUSE, MSG_PAUSE_GAD, 0, 1, 2, 1, 2, 6, MAIN_NARROW_TOGGLE, GID_DISABLE, MSG_DISABLE_GAD, 1, 1, 3, 1, 3, 7, MAIN_NARROW_TOGGLE, GID_OPENLOG, MSG_OPENLOG_GAD, 2, 1, 4, 0, 4, 8, MAIN_WIDE, GID_APPENDLOG, MSG_APPENDLOG_GAD, 2, 1, 4, 0, 4, 8, MAIN_WIDE_INVIS, GID_STARTLOG, MSG_STARTLOG_GAD, 2, 1, 4, 0, 4, 8, MAIN_WIDE_INVIS, GID_SERIALLOG, MSG_SERIALLOG_GAD, 2, 1, 4, 0, 4, 8, MAIN_WIDE_INVIS, GID_CLOSELOG, MSG_CLOSELOG_GAD, 2, 1, 4, 0, 4, 8, MAIN_WIDE_INVIS, GID_SETUP, MSG_SETUP_GAD, 3, 1, 5, 0, 6, 10, MAIN_WIDE, GID_SAVESET, MSG_SAVESET_GAD, 2, 2, 4, 1, 5, 9, MAIN_WIDE, GID_FUNCTION, MSG_FUNCTION_GAD, 3, 2, 5, 1, 7, 11, MAIN_WIDE, GID_STATUS, MSG_STATUS_GAD, 0, 0, 0, 0, 0, 0, MAIN_STATUS, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* * Names of gadgets in main window for calculating minimum width */ int MainWidthLeftText[] = { MSG_PAUSE_GAD, MSG_DISABLE_GAD, MSG_HIDE_GAD, MSG_QUIT_GAD, 0 }; int MainWidthRightText[] = { MSG_OPENLOG_GAD, MSG_APPENDLOG_GAD, MSG_STARTLOG_GAD, MSG_SERIALLOG_GAD, MSG_CLOSELOG_GAD, MSG_SAVESET_GAD, MSG_FUNCTION_GAD, MSG_SETUP_GAD, 0 }; /* * SnoopDos Menus * * -- Warning -- * * If you re-arrange the order of menu items or sub-items, you must * make sure to update the MENU_xxxx #define's at the end to reflect * the new ordering! * * (Remember to count bars when working out item numbers, and that * menu/item numbers start from zero, not one.) */ #define DIS NM_ITEMDISABLED #define TICK (CHECKIT | MENUTOGGLE) #define TICKED (CHECKIT | MENUTOGGLE | CHECKED) #define BAR NM_ITEM, NM_BARLABEL, NULL, 0, 0, NULL #define MENU_END NM_END, NULL, NULL, 0, 0, NULL #define M(type,title_id,flags,mid) \ type, (UBYTE *)(title_id), 0, flags, 0, (APTR)(mid) #define MX(type,title_id,flags,ex,mid) \ type, (UBYTE *)(title_id), 0, flags, ex,(APTR)(mid) struct NewMenu MainMenu[] = { /* * Project menu */ M( NM_TITLE, MSG_PROJECT_MENU, 0, 0), M( NM_ITEM, MSG_PROJECT_OPENLOG, 0, MID_OPENLOG), M( NM_ITEM, MSG_PROJECT_CLOSELOG, DIS, MID_CLOSELOG), BAR, MX( NM_ITEM, MSG_PROJECT_PAUSE, TICK, ~8, MID_PAUSE), MX( NM_ITEM, MSG_PROJECT_DISABLE, TICK, ~16, MID_DISABLE), BAR, M( NM_ITEM, MSG_PROJECT_STEP, 0, MID_STEP), BAR, M( NM_ITEM, MSG_PROJECT_PRI, 0, 0), MX( NM_SUB, MSG_PROJECT_PRI_A, TICK, ~1, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_B, TICK, ~2, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_C, TICK, ~4, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_D, TICK, ~8, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_E, TICK, ~16, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_F, TICK, ~32, MID_CHANGEPRI), MX( NM_SUB, MSG_PROJECT_PRI_G, TICK, ~64, MID_CHANGEPRI), BAR, M( NM_ITEM, MSG_PROJECT_HELP, 0, MID_HELP), M( NM_ITEM, MSG_PROJECT_ABOUT, 0, MID_ABOUT), BAR, M( NM_ITEM, MSG_PROJECT_HIDE, 0, MID_HIDE), BAR, M( NM_ITEM, MSG_PROJECT_QUIT, 0, MID_QUIT), /* * Windows menu */ M( NM_TITLE, MSG_WINDOWS_MENU, 0, 0), M( NM_ITEM, MSG_WINDOWS_SETUP, 0, MID_SETUP), M( NM_ITEM, MSG_WINDOWS_FUNCTION, 0, MID_FUNCTION), M( NM_ITEM, MSG_WINDOWS_FORMAT, 0, MID_FORMAT), BAR, M( NM_ITEM, MSG_WINDOWS_WIDTH, 0, 0), M( NM_SUB, MSG_WINDOWS_WIDTH_1, 0, MID_SETWIDTH), M( NM_SUB, MSG_WINDOWS_WIDTH_2, 0, MID_SETWIDTH), M( NM_SUB, MSG_WINDOWS_WIDTH_3, 0, MID_SETWIDTH), M( NM_SUB, MSG_WINDOWS_WIDTH_4, 0, MID_SETWIDTH), M( NM_SUB, MSG_WINDOWS_WIDTH_5, 0, MID_SETWIDTH), BAR, M( NM_ITEM, MSG_WINDOWS_SPACING, 0, 0), MX( NM_SUB, MSG_WINDOWS_SPACING_NONE, TICKED, ~1, MID_SPACE_NONE), MX( NM_SUB, MSG_WINDOWS_SPACING_1P, TICK, ~2, MID_SPACE_1P), MX( NM_SUB, MSG_WINDOWS_SPACING_2P, TICK, ~4, MID_SPACE_2P), M( NM_ITEM, MSG_WINDOWS_REFRESH, 0, 0), MX( NM_SUB, MSG_WINDOWS_REF_SIMPLE, TICK, ~1, MID_REF_SIMPLE), MX( NM_SUB, MSG_WINDOWS_REF_SMART, TICKED, ~2, MID_REF_SMART), M( NM_ITEM, MSG_WINDOWS_ALIGN, 0, 0), MX( NM_SUB, MSG_WINDOWS_ALIGN_LEFT, TICKED, ~1, MID_ALIGN_LEFT), MX( NM_SUB, MSG_WINDOWS_ALIGN_RIGHT, TICK, ~2, MID_ALIGN_RIGHT), M( NM_ITEM, MSG_WINDOWS_ROW_QUAL, 0, 0), MX( NM_SUB, MSG_WINDOWS_ROW_ANY, TICKED, ~1, MID_ROWQUAL), MX( NM_SUB, MSG_WINDOWS_ROW_NONE, TICK, ~2, MID_ROWQUAL), MX( NM_SUB, MSG_WINDOWS_ROW_SHIFT, TICK, ~4, MID_ROWQUAL), MX( NM_SUB, MSG_WINDOWS_ROW_ALT, TICK, ~8, MID_ROWQUAL), MX( NM_SUB, MSG_WINDOWS_ROW_CTRL, TICK, ~16, MID_ROWQUAL), MX( NM_SUB, MSG_WINDOWS_ROW_ALL, TICK, ~32, MID_ROWQUAL), BAR, M( NM_ITEM, MSG_WINDOWS_SHOWSTATUS, TICKED, MID_STATUS), M( NM_ITEM, MSG_WINDOWS_SHOWGADGETS, TICKED, MID_GADGETS), BAR, M( NM_ITEM, MSG_WINDOWS_AUTO_OPEN, TICK, MID_AUTO_OPEN), M( NM_ITEM, MSG_WINDOWS_DISABLE_HIDDEN, TICK, MID_DISABLE_HIDDEN), /* * Settings menu */ M( NM_TITLE, MSG_SETTINGS_MENU, 0, 0), M( NM_ITEM, MSG_SETTINGS_LOAD, 0, MID_LOAD), M( NM_ITEM, MSG_SETTINGS_SAVE, 0, MID_SAVE), M( NM_ITEM, MSG_SETTINGS_SAVEAS, 0, MID_SAVEAS), BAR, M( NM_ITEM, MSG_SETTINGS_RESETDEFAULTS, 0, MID_RESET), M( NM_ITEM, MSG_SETTINGS_LASTSAVED, 0, MID_LASTSAVED), M( NM_ITEM, MSG_SETTINGS_RESTORE, 0, MID_RESTORE), BAR, M( NM_ITEM, MSG_SETTINGS_CREATEICONS, TICK, MID_ICONS), /* * Buffer menu */ M( NM_TITLE, MSG_BUFFER_MENU, 0, 0), M( NM_ITEM, MSG_BUFFER_COPYWIN, 0, MID_COPYWIN), M( NM_ITEM, MSG_BUFFER_COPYBUF, 0, MID_COPYBUF), BAR, M( NM_ITEM, MSG_BUFFER_SAVEWIN, 0, MID_SAVEWIN), M( NM_ITEM, MSG_BUFFER_SAVEBUF, 0, MID_SAVEBUF), BAR, M( NM_ITEM, MSG_BUFFER_CLEARBUF, 0, MID_CLEARBUF), MENU_END }; /* * These defines are used when adjusting the menu settings on the fly */ #define MENU_OPENLOG FULLMENUNUM(0, 0, NOSUB) #define MENU_CLOSELOG FULLMENUNUM(0, 1, NOSUB) #define MENU_PAUSE FULLMENUNUM(0, 3, NOSUB) #define MENU_DISABLE FULLMENUNUM(0, 4, NOSUB) #define MENU_CHANGEPRI FULLMENUNUM(0, 8, 0) #define MENU_NUMPRI 7 /* Number of menu priorities */ #define MENU_HIDE FULLMENUNUM(0, 13, NOSUB) #define MENU_SPACING0 FULLMENUNUM(1, 6, 0) #define MENU_SPACING1 FULLMENUNUM(1, 6, 1) #define MENU_SPACING2 FULLMENUNUM(1, 6, 2) #define MENU_SIMPLE FULLMENUNUM(1, 7, 0) #define MENU_SMART FULLMENUNUM(1, 7, 1) #define MENU_AL_LEFT FULLMENUNUM(1, 8, 0) #define MENU_AL_RIGHT FULLMENUNUM(1, 8, 1) #define MENU_ROWQUAL_ANY FULLMENUNUM(1, 9, 0) #define MENU_ROWQUAL_NONE FULLMENUNUM(1, 9, 1) #define MENU_ROWQUAL_SHIFT FULLMENUNUM(1, 9, 2) #define MENU_ROWQUAL_ALT FULLMENUNUM(1, 9, 3) #define MENU_ROWQUAL_CTRL FULLMENUNUM(1, 9, 4) #define MENU_ROWQUAL_ALL FULLMENUNUM(1, 9, 5) #define MENU_STATUS FULLMENUNUM(1, 11, NOSUB) #define MENU_GADGETS FULLMENUNUM(1, 12, NOSUB) #define MENU_AUTO_OPEN FULLMENUNUM(1, 14, NOSUB) #define MENU_DIS_HIDDEN FULLMENUNUM(1, 15, NOSUB) #define MENU_ICONS FULLMENUNUM(2, 8, NOSUB) /***************************************************************************** * * Start of functions! * *****************************************************************************/ /* * GetTimeStr(datestamp) * * Returns a pointer to a string giving the current time in * hours, mins, and seconds. The string remains valid until * the next time this function is called. * * If the datestamp parameter is NULL, then the current date is * used instead. */ char *GetTimeStr(struct DateStamp *ds) { static char timestr[20]; struct DateStamp ourds; struct DateTime datetime; if (!ds) { ds = &ourds; DateStamp(ds); } datetime.dat_Stamp = *ds; datetime.dat_Format = FORMAT_DOS; datetime.dat_Flags = 0; datetime.dat_StrDay = NULL; datetime.dat_StrDate = NULL; datetime.dat_StrTime = timestr; DateToStr(&datetime); return (timestr); } /* * UpdateStatus(void) * * Determines what the current status is (by examining various * variables) and sets the status line to display an appropriate * message. Call whenever the status may have changed. * * Things which can cause a status change: pause/unpause, disable/enable, * open log/close log */ void UpdateStatus(void) { if (Paused || Disabled) { char *timestr; char *merid; char *msg; int hour; int min; if (Paused) { timestr = GetTimeStr(&PauseDateStamp); msg = MSG(MSG_STATUS_PAUSED); } else { timestr = GetTimeStr(&DisableDateStamp); msg = MSG(MSG_STATUS_DISABLED); } hour = atoi(timestr); min = atoi(timestr+3); merid = "AM"; if (hour >= 12) { hour -= 12; merid = "PM"; } mysprintf(StatusLineText, msg, (hour ? hour : 12), min, merid); } else if (LogActive) { int msgid; switch (CurrentLogType) { case LT_FILE: msgid = MSG_STATUS_LOGFILE; break; case LT_DEVICE: msgid = MSG_STATUS_LOGDEVICE; break; case LT_DEBUG: msgid = MSG_STATUS_LOGDEBUG; break; } mysprintf(StatusLineText, MSG(msgid), CurrentLogName); } else strcpy(StatusLineText, MSG(MSG_STATUS_NORMAL)); if (MainWindow) { GT_SetGadgetAttrs(Gadget[GID_STATUS], MainWindow, NULL, GTTX_Text, StatusLineText, TAG_DONE); } } /* * SetLogGadget(logmode, refresh) * * Displays the appropriate gadget in the main window according to the * type of logging method chosen. If the logfile is currently open, * the "close log" gadget is displayed, otherwise the correct "open" * gadget is displayed according to the logmode: LOGMODE_PROMPT, * LOGMODE_APPEND, LOGMODE_OVERWRITE, or LOGMODE_SERIALPORT * * If the refresh parameter is LG_REFRESH, then refresh gadgets. * LG_NOREFRESH means don't refresh gadgets (i.e. assume we're being * called from the gadget creation routine). */ void SetLogGadget(int logmode, int refresh) { static int loggadgets[] = { GID_OPENLOG, GID_APPENDLOG, GID_STARTLOG, GID_SERIALLOG, GID_CLOSELOG, 0 }; int gadid; int i; if (refresh == LG_REFRESH && !MainWindow) return; switch (logmode) { case LOGMODE_PROMPT: gadid = GID_OPENLOG; break; case LOGMODE_APPEND: gadid = GID_APPENDLOG; break; case LOGMODE_SERIALPORT: gadid = GID_SERIALLOG; break; case LOGMODE_OVERWRITE: gadid = GID_STARTLOG; break; } if (LogActive) gadid = GID_CLOSELOG; for (i = 0; loggadgets[i]; i++) { struct Gadget *gad = Gadget[loggadgets[i]]; int gadpos; if (refresh == LG_REFRESH) gadpos = RemoveGadget(MainWindow, gad); if (loggadgets[i] == gadid) gad->LeftEdge = LogButtonLeft; else gad->LeftEdge = INVIS_LEFT_EDGE; if (refresh == LG_REFRESH) { AddGadget(MainWindow, gad, gadpos); RefreshGList(gad, MainWindow, NULL, 1); } } LogButtonID = gadid; } /* * WriteLog(string) * * Writes the specified string to the logfile, handling buffering * etc. as required. * * If string is NULL, then flushes any buffers as necessary. */ void WriteLog(char *string) { int len; if (!LogActive) return; if (string == NULL) { /* * Flush output */ if (LogFile && LogBuffer && LogBufferPos) { Write(LogFile, LogBuffer, LogBufferPos); LogBufferPos = 0; } return; } if (CurrentLogType == LT_DEBUG) { /* * Output string to serial terminal, expanding LF's to CR/LF's * as we go. */ while (*string) { if (*string == '\n') RawPutChar('\r'); RawPutChar(*string++); } return; } len = strlen(string); if (LogBuffer) { /* * Buffered i/o so add string to buffer; if necessary, flush * buffer first. */ if ((LogBufferPos + len) > LogBufferSize) { Write(LogFile, LogBuffer, LogBufferPos); LogBufferPos = 0; } memcpy(LogBuffer + LogBufferPos, string, len); LogBufferPos += len; } else { /* * Plain unbuffered i/o */ Write(LogFile, string, strlen(string)); } } /* * CloseLog() * * Closes the currently open logfile (if any) */ void CloseLog(void) { char msg[100]; if (!LogActive) return; mysprintf(msg, MSG(MSG_LOG_STOP), GetTimeStr(NULL)); WriteLog(msg); WriteLog(NULL); /* Flush output */ LogActive = 0; SetLogGadget(DefaultLogMode, LG_REFRESH); UpdateStatus(); SetMenuOptions(); if (LogFile) { int oldpaused = Paused; Paused = 0; Close(LogFile); LogFile = NULL; Paused = oldpaused; } if (LogBuffer) { FreeMem(LogBuffer, LogBufferSize); LogBuffer = NULL; } } /* * WriteLogHeader() * * Writes the standard SnoopDos header line to the logfile using the * current logfile format */ void WriteLogHeader(void) { FormatEvent(LogEFormat, NULL, BufferLine+1, 0, LogWidth - 1); BufferLine[0] = ' '; BufferLine[LogWidth+1] = '\n'; BufferLine[LogWidth+2] = 0; WriteLog(BufferLine); UnderlineTitles(LogEFormat, BufferLine+1, '-'); BufferLine[LogWidth+1] = '\n'; WriteLog(BufferLine); } /* * OpenLog(mode, filename) * * Opens the named file for logging. If a log file is already open, * then closes the existing file first. * * Mode is set to: * * LOGMODE_PROMPT - Prompt user if file exits for append/overwrite * LOGMODE_APPEND - Automatically append to file if it exists * LOGMODE_OVERWRITE - Automatically overwrite file it if exitsts * LOGMODE_SERIALPORT - Direct output to debugger on serial port * * Filename is ignored if mode is set to LOGMODE_SERIALPORT. * * Returns 1 for success, 0 for failure (couldn't open file?) */ int OpenLog(int logmode, char *filename) { APTR oldwinptr = *TaskWindowPtr; char startmsg[100]; char timestr[20]; char datestr[20]; char weekday[20]; struct DateTime datetime; int pausestate = Paused; Paused = 0; if (LogActive) CloseLog(); if (logmode == LOGMODE_SERIALPORT) { CurrentLogType = LT_DEBUG; } else { int filemode; int buffer = 0; strcpy(CurrentLogName, filename); *TaskWindowPtr = (APTR)-1; if (IsFileSystem(filename)) CurrentLogType = LT_FILE; else CurrentLogType = LT_DEVICE; *TaskWindowPtr = oldwinptr; if (logmode == LOGMODE_PROMPT && CurrentLogType == LT_FILE) { BPTR lock = Lock(filename, ACCESS_READ); if (lock) { UnLock(lock); switch (GetResponse(MSG(MSG_REQ_APPEND_OVERWRITE_CANCEL), MSG(MSG_REQ_FILE_EXISTS), filename)) { case 1: filemode = MODE_READWRITE; break; case 2: filemode = MODE_NEWFILE; break; default: goto open_okay; } } else filemode = MODE_NEWFILE; } else if (logmode == LOGMODE_APPEND) filemode = MODE_READWRITE; else filemode = MODE_NEWFILE; LogFile = Open(filename, filemode); if (!LogFile) goto open_failed; if (filemode == MODE_READWRITE) { Seek(LogFile, 0, OFFSET_END); Write(LogFile, "\n", 1); /* Leave space between multiple logs */ } /* * Now select buffer type for file according to gadget and * file type. */ switch (CurSettings.Setup.FileIOType) { case FILE_AUTOMATIC: if (CurrentLogType == LT_FILE) buffer = 1; break; case FILE_IMMEDIATE: buffer = 0; break; case FILE_BUFFERED: buffer = 1; break; } if (buffer) { /* * Allocate a buffer for file i/o. Doesn't matter if * it fails -- we just drop back to unbuffered file * i/o instead. */ LogBufferSize = LOGBUFFER_SIZE; LogBufferPos = 0; LogBuffer = AllocMem(LogBufferSize, MEMF_ANY); } } LogActive = 1; SetLogGadget(DefaultLogMode, LG_REFRESH); UpdateStatus(); SetMenuOptions(); DateStamp(&datetime.dat_Stamp); datetime.dat_Format = FORMAT_DOS; datetime.dat_Flags = 0; datetime.dat_StrDay = weekday; datetime.dat_StrDate = datestr; datetime.dat_StrTime = timestr; DateToStr(&datetime); mysprintf(startmsg, MSG(MSG_LOG_START), weekday, datestr, timestr); /* * Now select appropriate output format for log */ LogWidth = ParseFormatString(LogFormat, LogEFormat, MAX_FORM_LEN); if (!LogWidth) LogWidth = ParseFormatString(BufFormat, LogEFormat, MAX_FORM_LEN); WriteLog(startmsg); WriteLogHeader(); open_okay: Paused = pausestate; return (1); open_failed: Paused = pausestate; return (0); } /* * SaveBuffer(savetype, savename, overwrite) * * This function saves the contents of some or all of the current * buffer to a disk file or to the clipboard. * * savetype is SAVEBUF_WINDOW to save only that portion of the * buffer that is currently displayed in the main window, or * SAVEBUF_ALL to save the entire buffer. * * savename is a pointer to a disk filename, or SAVEBUF_CLIPBOARD to * save to the clipboard.device. * * overwrite is SAVEBUF_OVERWRITE to force an existing file to be * overwritten automatically or SAVEBUF_PROMPT to let the user * choose whether to overwrite using a requester. If the user * chooses not to overwrite, then success is returned. * * Returns 1 for success or 0 for failure. */ int SaveBuffer(int savetype, char *savename, int overwrite) { struct IOClipReq *clipreq; struct MsgPort *clipport; BPTR savefile; UBYTE *savebuf; Event *curev; ULONG curpos; int startseq; int curseq; int endseq; int leftcol; int rightcol; int totalsize; int width; int numlines; if (savetype == SAVEBUF_WINDOW) { /* * Save only visible portion of buffer */ startseq = TopSeq; curev = TopEvent; endseq = BottomSeq; leftcol = LeftCol; rightcol = RightCol; } else { /* * Save entire buffer */ startseq = RealFirstSeq; curev = HeadNode(&EventList); endseq = EndSeq; leftcol = 0; rightcol = BufferWidth - 1; } savebuf = AllocMem(SAVEBUFFER_SIZE, MEMF_ANY); if (!savebuf) return (0); curpos = 0; /* * Now try and open our output device */ if (savename == SAVEBUF_CLIPBOARD) { /* * Open clipboard */ clipport = CreateMsgPort(); if (!clipport) { FreeMem(savebuf, SAVEBUFFER_SIZE); return (0); } clipreq = (struct IOClipReq *)CreateExtIO(clipport, sizeof(*clipreq)); if (!clipreq) { FreeMem(savebuf, SAVEBUFFER_SIZE); DeleteMsgPort(clipport); return (0); } if (OpenDevice("clipboard.device", 0, (void *)clipreq, 0)) { FreeMem(savebuf, SAVEBUFFER_SIZE); DeleteExtIO((struct IORequest *)clipreq); DeleteMsgPort(clipport); return (0); } clipreq->io_Error = 0; clipreq->io_Offset = 0; clipreq->io_ClipID = 0; } else { /* * Open log file */ int filemode = MODE_NEWFILE;; if (overwrite == SAVEBUF_PROMPT) { BPTR lk = Lock(savename, ACCESS_READ); if (lk) { UnLock(lk); switch (GetResponse(MSG(MSG_REQ_APPEND_OVERWRITE_CANCEL), MSG(MSG_REQ_FILE_EXISTS), savename)) { case 1: filemode = MODE_READWRITE; break; case 2: filemode = MODE_NEWFILE; break; default: FreeMem(savebuf, SAVEBUFFER_SIZE); return (1); } } } savefile = Open(savename, filemode); if (!savefile) { FreeMem(savebuf, SAVEBUFFER_SIZE); return (0); } if (filemode == MODE_READWRITE) { Seek(savefile, 0, OFFSET_END); Write(savefile, "\n", 1); /* Leave blank line between appends */ } } /* * Now calculate how many lines there are to be saved in the * buffer. There will be as many lines as were selected from * startseq to endseq (inclusive) plus two additional lines for * the header and underline. Each line will have (rightcol-leftcol+1) * characters on it, plus one additional character for the newline. * We need to calculate all this in advance because the header * written for the clipboard includes the number of characters of * text to be written. */ width = (rightcol - leftcol + 1); if (IsListEmpty(&EventList)) numlines = 2; else numlines = (endseq - startseq + 1) + 2; totalsize = (width + 1) * numlines; if (savename == SAVEBUF_CLIPBOARD) { /* * Write the clipboard FTXT header into the buffer. We * need to write the amount of text, but we also need * to write the total size of the clip (including header) * rounded to a word boundary. */ int cliplen = (totalsize + 13) & ~1; /* Includes header */ memcpy(savebuf, "FORM....FTXTCHRS....", 20); *((ULONG *)&savebuf[4]) = cliplen; *((ULONG *)&savebuf[16]) = totalsize; curpos += 20; } /* * Next, output the two header lines (the column headings plus * the underline) */ FormatEvent(BufferEFormat, NULL, savebuf + curpos, leftcol, rightcol); curpos += width; savebuf[curpos++] = '\n'; UnderlineTitles(BufferEFormat, BufferLine, '-'); memcpy(savebuf + curpos, BufferLine + leftcol, width); curpos += width; savebuf[curpos++] = '\n'; /* * Now walk through the lines in the buffer, outputting each line * one at a time. */ curseq = startseq; totalsize -= (width + width + 2); /* Allow for header */ while (totalsize > 0) { /* * First check if we have enough room in the buffer for * the next line -- if not, then flush the buffer */ if ((curpos + width + 2) >= SAVEBUFFER_SIZE) { if (savename == SAVEBUF_CLIPBOARD) { clipreq->io_Command = CMD_WRITE; clipreq->io_Data = savebuf; clipreq->io_Length = curpos; DoIO((struct IORequest *)clipreq); } else Write(savefile, savebuf, curpos); curpos = 0; } /* * The next section must be completely enclosed in * ObtainSemaphore/ReleaseSemaphore to ensure the * buffer doesn't change under our feet */ ObtainSemaphore(&BufSem); if (curseq < RealFirstSeq) { /* * Uhoh -- the list has scrolled past us while we * were outputting it; catch up as best we can. */ curev = HeadNode(&EventList); if (NextNode(curev)) curseq = curev->seqnum; else curseq = endseq + 1; } if (curseq > endseq) { /* * We've somehow run out of lines -- this should never * happen, but you never know. For disk files, we can * just avoid outputting anything else. For the * clipboard, we need to output something, so we just * output a line of spaces. */ if (savename != SAVEBUF_CLIPBOARD) break; memset(savebuf + curpos, ' ', width); } else { FormatEvent(BufferEFormat, curev, savebuf + curpos, leftcol, rightcol); curev = NextNode(curev); curseq++; } curpos += width; savebuf[curpos++] = '\n'; totalsize -= (width + 1); ReleaseSemaphore(&BufSem); } /* * Now we've output the entire save region, so just flush anything * left in the save buffer and we're done. */ if (savename == SAVEBUF_CLIPBOARD) { /* * Make sure out total output is word-aligned, otherwise * Bad Things will happen */ if (totalsize & 1) savebuf[curpos++] = '\0'; if (curpos > 0) { clipreq->io_Command = CMD_WRITE; clipreq->io_Data = savebuf; clipreq->io_Length = curpos; DoIO((struct IORequest *)clipreq); } clipreq->io_Command = CMD_UPDATE; DoIO((struct IORequest *)clipreq); /* Make data publically available */ CloseDevice((struct IORequest *)clipreq); DeleteExtIO((struct IORequest *)clipreq); DeleteMsgPort(clipport); } else { if (curpos) Write(savefile, savebuf, curpos); Close(savefile); } FreeMem(savebuf, SAVEBUFFER_SIZE); return (1); } /* * SetMonitorMode(type) * * Sets the current monitor mode to the specified type. This will * be one of the following three: * * MONITOR_NORMAL * MONITOR_PAUSED * MONITOR_DISABLED * * Automatically takes care of updating the gadget imagery and * menu options to reflect the mode. Also ignores attempts to * Pause() if the main window isn't currently open. Note that * the three modes are mutually exclusive. * * The status line is also updated to reflect the current mode. * The global MonitorType reflects the currently selected mode * after this call. * * Important: any call which may possible spawn a subtask * (e.g. showing an ASL file requester etc.) must force the Paused * variable to 0 before hand and then restore it to its original * setting afterwards. This ensures that any other tasks that were * already paused remain paused, but any new tasks that make calls * during the activity will not be paused. * * This isn't perfect, but it gives about 99% certainty of avoiding * deadlocks, while still ensuring that any existing paused tasks * (normally just one or two) remain paused. Thus, if someone has * paused some stuff so they can open a log file, turn on the * printer, or whatever, it will remain paused. (The possibility of * deadlock comes about if our external file i/o somehow causes * another process to make a call to one of the monitored SnoopDos * functions, e.g. OpenLibrary or OpenDevice). * * The alternative is to completely disable pausing on such calls, in * which case vast quantities of output might be generated while the * person is messing around in the file requester oblivious. * * NOTE: It is impossible to enter PAUSED mode unless the SnoopDos * window is open -- this is to prevent situations where you freeze * the system (e.g. it's waiting for you to unpause, but you can't * because you can't call up the main window due to something else.) */ void SetMonitorMode(int modetype) { int paused = 0; int disabled = 0; int changed = 0; if (!MainWindow && modetype == MONITOR_PAUSED) return; if (modetype == MONITOR_DISABLED) disabled = 1; else if (modetype == MONITOR_PAUSED) paused = 1; /* * Now update main window gadgets if necessary */ if (MainWindow) { if (paused != Paused) { ShowGadget(MainWindow, Gadget[GID_PAUSE], (paused ? GADGET_DOWN : GADGET_UP)); } if (disabled != Disabled) { ShowGadget(MainWindow, Gadget[GID_DISABLE], (disabled ? GADGET_DOWN : GADGET_UP)); } } /* * Next, make the changes actually take effect. */ if (paused != Paused) { DateStamp(&PauseDateStamp); if (paused) ObtainSemaphore(&PauseSem); else ReleaseSemaphore(&PauseSem); Paused = paused; changed = 1; } if (disabled != Disabled) { DateStamp(&DisableDateStamp); Disabled = disabled; changed = 1; LoadFuncSettings(&CurSettings.Func); /* Update patches state */ if (LogActive) { char msg[100]; mysprintf(msg, MSG(Disabled ? MSG_LOG_DISABLED : MSG_LOG_ENABLED), GetTimeStr(NULL)); WriteLog(msg); WriteLog(NULL); /* Flush output to keep everything up-to-date */ } } if (changed) { if (LogActive) /* Flush log file when Paused */ WriteLog(NULL); UpdateStatus(); SetMenuOptions(); } MonitorType = modetype; } /* * SingleStep() * * Undoes Pause just long enough for all currently waiting tasks * to execute a single monitored event. Usually, this will just * produce a single event in the event buffer. * * Why would you want to do this? Because it's a real handy way * of single-stepping through a program's actions at startup, * especially if it happens to be crashing after doing a particular * action! * * We implement the singlestep by freeing up the Pause semaphore * and then immediately trying to recapture it. We rely on the * fact that Exec schedules semaphore operations on a strictly * first-come first-served basis, so as soon as we free it, * all the waiting tasks are guaranteed to run before we can * recapture it. We move ourselves to a high priority temporarily * to ensure that nobody gets a chance to run two events before * we can regain control again. * * If we weren't in pause mode before, we will be when we exit. * */ void SingleStep(void) { int oldpri; if (!MainWindow) return; if (!Paused) SetMonitorMode(MONITOR_PAUSED); WriteLog(NULL); /* Make sure logfile is up to date! */ /* * To ensure that we get the semaphore back as soon as we * possibly can (i.e. let all the waiting tasks run but nobody * else), we bump our priority up temporarily */ oldpri = SetTaskPri(SysBase->ThisTask, 25); ReleaseSemaphore(&PauseSem); ObtainSemaphore(&PauseSem); SetTaskPri(SysBase->ThisTask, oldpri); } /* * CheckForScreen() * * Checks if we've already got a SnoopDos screen open -- if we do, * then returns TRUE. If we don't, then tries to lock the current * screen and returns TRUE. If we can't lock the screen, then * displays an error message and returns FALSe. */ int CheckForScreen(void) { if (!SnoopScreen) { SetupScreen(); if (!SnoopScreen) { ShowError(MSG(MSG_ERROR_NO_SCREEN)); return (FALSE); } } return (TRUE); } /* * ShowSnoopDos() * * Attempts to open SnoopDos on the user's current preferred screen. * If SnoopDos is already running, but on a different screen, then * it is moved to this screen. Any open SnoopDos windows are moved, * though any AmigaGuide help remains on the previous screen until * the next time help is requested. * * Fails if we can't lock the new screen for some reason (this * should never happen!) */ int ShowSnoopDos(void) { struct Screen *prevscr = SnoopScreen; struct Window *prevfunc = FuncWindow; struct Window *prevform = FormWindow; struct Window *prevset = SetWindow; if (!SetupScreen()) { ShowError(MSG(MSG_ERROR_NO_SCREEN)); return (FALSE); } RemoveProgramFromWorkbench(); ScreenToFront(SnoopScreen); if (prevscr == SnoopScreen) { /* * We were already running, and we're on the * same screen, so nothing else to do. For good * measure, we bring the main window to the * foreground, however. */ OpenMainWindow(); return (TRUE); } if (prevscr != NULL) { /* * We're moving from a previous screen to this new screen, * so bring all the windows with us */ CleanupSubWindows(); CloseMainWindow(); OpenMainWindow(); if (prevset) OpenSettingsWindow(); if (prevfunc) OpenFunctionWindow(); if (prevform) OpenFormatWindow(); } else { /* * We're opening on an entirely new screen -- if * DisableOnHide is true, then we need to restore * the saved state of Disable/Enable (since it will * have been disabled while in the background) */ if (DisableOnHide) SetMonitorMode(LastKnownState); OpenMainWindow(); } return (TRUE); } /* * HideSnoopDos() * * Removes all SnoopDos windows from the screen. If DisableOnHide * is true, then SnoopDos is put into a disabled state. Otherwise, * if we're currently paused, then SnoopDos is unpaused (since * otherwise, it would be easy to get into a situation where * everything was frozen and you could do nothing to bring * SnoopDos back to life). * * If commodities library hasn't been opened, then we don't hide * since that would make it difficult to resurrect SnoopDos again. * However, if commodities library is open but the user has given * an invalid hotkey, we do hide -- they can always use commodities * exchange to reactivate it. */ void HideSnoopDos(void) { LastKnownState = MonitorType; if (CurSettings.Setup.HideMethod == HIDE_NONE) return; CleanupSubWindows(); CloseMainWindow(); CleanupScreen(); if (DisableOnHide) SetMonitorMode(MONITOR_DISABLED); else if (Paused) SetMonitorMode(MONITOR_NORMAL); if (CurSettings.Setup.HideMethod != HIDE_INVIS) AddProgramToWorkbench(CurSettings.Setup.HideMethod); } /* * InitMenus() * * Should be called once as soon as the language file has been * read in. Walks down the menu structure and replaces the * message ID's with actual pointers to the message. */ void InitMenus(void) { struct NewMenu *menu; for (menu = &MainMenu[0]; menu->nm_Type != NM_END; menu++) { if (menu->nm_Label && menu->nm_Label != NM_BARLABEL) { char *msg = MSG((ULONG)(menu->nm_Label)); menu->nm_Label = msg + 2; if (*msg && *msg != ' ') menu->nm_CommKey = msg; } } } /* * CalcMinMainSize(gadgetfa, bufferfa, &width, &height) * * Fills in width and height with the minimum allowable dimensions * the main window can obtain using the specified gadget and buffer * fonts. Returns TRUE for success, FALSE for failure (e.g. window * to big to fit on screen using current fonts). */ BOOL CalcMinMainSize(struct TextAttr *gadgetfa, struct TextAttr *bufferfa, int *pwidth, int *pheight) { struct TextFont *gfont; struct TextFont *bfont; int widthleft; int widthright; int width; int height; int fonty; int bfonty; int bfontx; int bordright = SizeImage->Width; int bordbot = SizeImage->Height; gfont = MyOpenFont(gadgetfa); if (!gfont) return (FALSE); bfont = MyOpenFont(bufferfa); if (!bfont) { CloseFont(gfont); return (FALSE); } if (bfont->tf_Flags & FPF_PROPORTIONAL) { CloseFont(bfont); CloseFont(gfont); return (FALSE); } fonty = gfont->tf_YSize; bfonty = bfont->tf_YSize; bfontx = bfont->tf_XSize; widthleft = MaxTextLen(gfont, MainWidthLeftText) + 12; widthright = MaxTextLen(gfont, MainWidthRightText) + 12; width = (widthleft + widthright + MAIN_MARGIN) * 2 + BorderLeft + bordright; height = TitlebarHeight + (bfonty + BoxInterGap) * 3 + bordbot + 20 + (fonty + 10) * 3; CloseFont(bfont); CloseFont(gfont); if (width > ScreenWidth || height > ScreenHeight) return (FALSE); *pwidth = width; *pheight = height; return (TRUE); } /* * SetupBufferRastPort() * * Sets up the main window rastport to have the correct font and * colours for rendering. Should always be called before OutputBufLine * if gadget operations may have occurred since the last redraw. */ void SetupBufferRastPort(void) { struct RastPort *rport = MainWindow->RPort; SetAPen(rport, 1); SetBPen(rport, 0); SetDrMd(rport, JAM2); SetFont(rport, BufferFont); } /* * DrawHeaderLine() * * Redraws the current event header line in the top part of the window * * If a column is currently selected for dragging, then draws the * column heading in white to indicate it's active. */ void DrawHeaderLine(void) { struct RastPort *rport = MainWindow->RPort; int numcols = RightCol - LeftCol + 1; if (ClearMainRHS) numcols = BoxCols; SetupBufferRastPort(); FormatEvent(BufferEFormat, NULL, BufferLine, LeftCol, LeftCol+numcols-1); /* * We mark the very last position on the header line with a single * dot, which can then be grabbed by the user to resize the width * of the last column. We don't overwrite the actual text of the * final header, if it's narrow. * * If the columns are currently being dragged, we display a | instead * of a dot, to make it easier to spot, and we display it even if * it would overwrite some of the final heading. */ if (DraggingColumn) { /* * Draw the currently selected column with a white heading, * and the other stuff with a black heading */ int selectwidth = SelectEvent->width; if ((SelectEvent+1)->type != EF_END) { /* * If we're not highlighting the last column already * then indicate the end of the last column with a * little vertical bar */ BufferLine[BufferWidth - LeftCol - 1] = '|'; } if (SelectStartCol > LeftCol) { /* * Draw black portion to left of selected column */ int width = MIN(SelectStartCol - LeftCol, numcols); Move(rport, BoxInLeft, BoxHeaderLine); Text(rport, BufferLine, width); } if ((SelectStartCol + selectwidth) < (LeftCol + numcols)) { /* * Draw black portion to right of selected column */ int xoffset = SelectStartCol + selectwidth - LeftCol; if (xoffset < 0) xoffset = 0; Move(rport, BoxInLeft + BoxCharWidth * xoffset, BoxHeaderLine); Text(rport, BufferLine + xoffset, numcols - MAX(xoffset,0)); } if (SelectStartCol >= LeftCol && SelectStartCol < (LeftCol+numcols)) { /* * Draw selected column heading in white */ int xoffset = SelectStartCol - LeftCol; int fwidth = MIN(selectwidth, numcols - xoffset); if (fwidth > 0) { SetAPen(rport, 2); Move(rport, BoxInLeft + xoffset * BoxCharWidth, BoxHeaderLine); Text(rport, BufferLine + xoffset, fwidth); SetAPen(rport, 1); } } else if (SelectStartCol < LeftCol && (SelectStartCol + selectwidth) >= LeftCol) { /* * Draw portion of selected column heading in white */ int colwidth = SelectStartCol + selectwidth - LeftCol; SetAPen(rport, 2); Move(rport, BoxInLeft, BoxHeaderLine); Text(rport, BufferLine, MIN(colwidth, numcols)); SetAPen(rport, 1); } } else { /* * Not dragging a column, so draw entire line in black */ Move(rport, BoxInLeft, BoxHeaderLine); Text(rport, BufferLine, numcols); } } /* * RedrawMainWindow() * * Redraws all the main parts of the window, except gadgets. */ void RedrawMainWindow(void) { struct RastPort *rport; if (!MainWindow) return; rport = MainWindow->RPort; DrawBevelBox(rport, BoxLeft, MainWindow->BorderTop, BoxWidth, BoxTop - MainWindow->BorderTop, GT_VisualInfo, MainVI, TAG_DONE); DrawBevelBox(rport, BoxLeft, BoxTop, BoxWidth, BoxHeight, GT_VisualInfo, MainVI, TAG_DONE); /* * We only draw the third bevel box if either the status line or * gadget line is enabled. */ if (StatusLine || GadgetsLine) { DrawBevelBox(rport, BoxLeft, BoxTop + BoxHeight, BoxWidth, MainWindow->Height - MainWindow->BorderBottom - BoxTop - BoxHeight, GT_VisualInfo, MainVI, TAG_DONE); } DrawHeaderLine(); ShowBuffer(TopSeq, DISPLAY_ALL); if (ClearMainRHS) { if ((RightCol-LeftCol+1) < BoxCols) { /* * Erase strip to right of our rendered text */ SetAPen(rport, 0); SetDrMd(rport, JAM1); RectFill(rport, BoxInLeft + (RightCol-LeftCol+1) * BoxCharWidth, BoxInTop, BoxInLeft + BoxInWidth - 1, BoxInTop + BoxInHeight - 1); } ClearMainRHS = 0; } } /* * SetTextSpacing(newspacing) * * Updates the spacing of the main window to reflect the new spacing * value passed in, and adjust all the global variables accordingly. */ void SetTextSpacing(int newspacing) { int bfonty = BufferFont->tf_YSize; int bfontbaseline = BufferFont->tf_Baseline; int maxheight; if (BoxInterGap == newspacing) return; /* No change needed */ ChangedSpacing = 1; /* Flag for resize routine */ BoxInterGap = newspacing; if (!MainWindow) return; SetAPen(MainWindow->RPort, 0); SetDrMd(MainWindow->RPort, JAM1); RectFill(MainWindow->RPort, BoxInLeft, BoxInTop, BoxInLeft + BoxInWidth - 1, BoxInTop + BoxInHeight - 1); maxheight = BoxHeight - VBorderGap * 2 - 2; BoxSpacing = bfonty + BoxInterGap; BoxRows = (maxheight + BoxInterGap) / BoxSpacing; BoxInHeight = BoxRows * BoxSpacing - BoxInterGap; BoxInTop = BoxTop + VBorderGap + 1; BoxBaseline = BoxInTop + bfontbaseline; /* * We now check to see if we're currently at the end of the buffer, * and if so, make sure we stay that way by repositioning after the * line change. */ if (BottomSeq >= EndSeq) ShowBuffer(EndSeq, DISPLAY_NONE); LOCK_LAYERS; RedrawMainWindow(); UNLOCK_LAYERS; UpdateMainVScroll(); } /* * InitMainMargins() * * Initialises the window margins according to the currently selected * left offset and the current buffer width (as determined by the * format string). Automatically ensures margins are kept within * the bounds of the displayable area, adjusting them as necessary. * * On entry, LeftCol should contain the current left margin, BoxCols * should be initialised to the current width of the displayable * buffer area, and BufferWidth should be the maximum width of the * currently selected format string. */ void InitMainMargins(void) { if ((LeftCol + BoxCols) > BufferWidth) LeftCol = BufferWidth - BoxCols; if (LeftCol < 0) LeftCol = 0; RightCol = LeftCol + BoxCols - 1; if (RightCol >= BufferWidth) { RightCol = BufferWidth - 1; if (RightCol < 0) RightCol = 0; } } /* * FreeScrollGadgets() * * Frees all associated BOOPSI objects and images used by the * scroll gadgets. Must only be called after the main window * has closed. */ void FreeScrollGadgets(void) { int i; for (i = GID_STARTSCROLL; i <= GID_ENDSCROLL; i++) { if (Gadget[i]) { DisposeObject(Gadget[i]); Gadget[i] = NULL; } } for (i = 0; i < 4; i++) { if (ScrollImage[i]) { DisposeObject(ScrollImage[i]); ScrollImage[i] = NULL; } } } /* * FreeMainGadgets() * * Frees all the gadgets allocated for the main window. */ void FreeMainGadgets(void) { if (BufferFont) { CloseFont(BufferFont); BufferFont = NULL; } if (MainGadList) { FreeGadgets(MainGadList); MainGadList = NULL; } } /* * CreateScrollGadgets() * * Creates the list of six gadgets that are used to give our window * scroll bars and scroll arrows. These have to be created independently * of the GadTools button gadgets, because since they are in the border, * they must be added to the window at the time it is opened, and not * afterwards. * * The GadTools gadgets are removed and then re-added (with new * size-sensitive positions) whenever the window is resized; if we * try and do this with the BOOPSI border gadgets however, Intuition * gets the positioning subtely wrong the second and subsequent times. * * Hence you must call this function before opening the main window. All * the gadgets are created relative to the current window dimensions, and * so do not depend on specific hardcoded with/height values. * * Returns a pointer to the gadget list, or NULL if the gadgets * couldn't be created. */ struct Gadget *CreateScrollGadgets(void) { struct Gadget *gadlist = NULL; struct Gadget *gad = (struct Gadget *)&gadlist; int sizew = SizeImage->Width; int sizeh = SizeImage->Height; int bw = (ScreenResolution == SYSISIZE_LOWRES) ? 1 : 2; int i; /* * First, create the arrows for the scroll bars. Gad starts off * pointing to gadlist, which will receive the pointer to the * first gadget we create. */ ScrollArrows[0].widthoffset = ScrollArrows[1].widthoffset = 1 - sizew; ScrollArrows[2].heightoffset = ScrollArrows[3].heightoffset = 1 - sizeh; for (i = 0; i < 4; i++) { struct ScrollData *scd = &ScrollArrows[i]; int gadid = scd->gadgetid; struct Image *img; img = (struct Image *) NewObject(NULL, "sysiclass", SYSIA_DrawInfo, ScreenDI, SYSIA_Which, scd->imagetype, SYSIA_Size, ScreenResolution, TAG_END); if (!img) goto sgad_failed; ScrollImage[i] = img; gad = (struct Gadget *) NewObject(NULL, "buttongclass", GA_ID, gadid, GA_Immediate, TRUE, GA_Image, img, GA_Width, img->Width, GA_Height, img->Height, GA_Previous, gad, GA_RelVerify, TRUE, scd->borderpos, TRUE, GA_RelRight, scd->widthscale * img->Width + scd->widthoffset, GA_RelBottom, scd->heightscale * img->Height + scd->heightoffset, ICA_TARGET, ICTARGET_IDCMP, TAG_END); if (!gad) goto sgad_failed; Gadget[gadid] = gad; } /* * Now create our two BOOPSI scroll gadgets in the window border */ gad = (struct Gadget *)NewObject(NULL, "propgclass", GA_ID, GID_HSCROLLER, PGA_Freedom, FREEHORIZ, PGA_NewLook, TRUE, PGA_Borderless, TRUE, PGA_Top, LeftCol, PGA_Visible, BoxCols, PGA_Total, BufferWidth, GA_Left, 3, GA_RelBottom, 3 - sizeh, GA_RelWidth, -sizew - 5 - ScrollImage[0]->Width - ScrollImage[1]->Width, GA_Height, sizeh - 4, GA_Previous, gad, GA_BottomBorder, TRUE, ICA_TARGET, ICTARGET_IDCMP, ICA_MAP, RZ_MapTags, TAG_END); if (!gad) goto sgad_failed; Gadget[GID_HSCROLLER] = gad; gad = (struct Gadget *)NewObject(NULL, "propgclass", GA_ID, GID_VSCROLLER, PGA_Freedom, FREEVERT, PGA_NewLook, TRUE, PGA_Borderless, TRUE, PGA_Top, TopSeq - FirstSeq, PGA_Visible, BoxRows, PGA_Total, EndSeq - FirstSeq + 1, GA_RelRight, bw - sizew + 3, GA_Top, TitlebarHeight + 1, GA_Width, sizew - bw - bw - 4, GA_RelHeight, -TitlebarHeight - ScrollImage[2]->Height- ScrollImage[3]->Height- sizeh - 2, GA_RightBorder, TRUE, GA_Previous, gad, ICA_TARGET, ICTARGET_IDCMP, ICA_MAP, RZ_MapTags, TAG_END); if (!gad) goto sgad_failed; Gadget[GID_VSCROLLER] = gad; return (gadlist); sgad_failed: FreeScrollGadgets(); return (NULL); } /* * CreateMainGadgets(width, height, statusline) * * Creates the gadgets for the main SnoopDos window, assuming a window * of size width x height. MainGadList will point to the list of * gadgets when completed. Returns NULL if unsuccessful, or MainGadList * if successful. * * Statusline is a flag that says whether or not the status gadget is * to be created. * * There are basically seven possible gadget layouts: * * - 4 x 3 grid with status line on top * - 4 x 2 grid with no status line * - 6 x 2 grid with status line occupying the upper 4 left spaces * - 8 x 1 grid with no status line * - 12 x 1 grid with status line * - 0 grid with no gadgets or status line * - 1 line grid with status line but no gadgets * * The first two are used when the window is narrow; the second two * when the window is wider. We use colwidth[0] to hold the width * of the narrow gadgets, colwidth[1] for the wide gadgets, and * colwidth[2] for the status line gadget. * * To allow us to conveniently replace one gadget definition with * another, we use the concept of invisible buttons -- these are * like normal buttons, but their X position is set way off to the * left of the window where they won't be displayed; to reveal * the buttons, we simply restore their X position and make the * X position of a different button negative. * * Finally, we have toggle buttons which flip flop between on and off. * These are defined as MAIN_NARROW_TOGGLE. */ struct Gadget *CreateMainGadgets(struct TextAttr *gadgetfa, struct TextAttr *bufferfa, int width, int height, int statusline) { struct MainGadgets *mg; struct NewGadget ng; struct TextFont *font; struct Gadget *gadlist; struct Gadget *gad; UWORD colwidth[MAIN_NUMWIDTHS]; WORD colpos[12]; UWORD spacing; /* Spacing between gadget tops */ UWORD yoffset; /* Offset of first gadget top in window */ int fonty; /* Y height of gadget font */ int bfontx; /* X height of buffer font */ int bfonty; /* Y height of buffer font */ int bfontbaseline; /* Baseline of buffer font */ int innerwidth; /* Usable width of window */ int extraspace; /* Spare space inbetween buffer and gadgets */ int gheight; /* Height of a single gadget */ int width4; /* Minimum width needed for 4 x 3 gadgets */ int width6; /* Minimum width needed for 6 x 2 gadgets */ int width8; /* Minimum width needed for 8 x 1 gadgets */ int width12; /* Minimum width needed for 12 x 1 gadgets */ int gridtype; /* Grid type for gadget layout */ int numrows; /* Number of rows of gadgets involved */ int gadg_gap; /* Aspect-corrected gap between gadget rows */ int margin = MAIN_MARGIN; /* Gadget margin at left edge of window */ int boxmaxwidth; /* Temporary width of listview box */ int boxheight; /* Temporary height of listview box */ int bordright = SizeImage->Width; int bordbot = SizeImage->Height; /* * Quick defines for our internal grid arrangements */ #define FOUR_BY_THREE 0 #define SIX_BY_TWO 1 #define EIGHT_BY_ONE 2 #define TWELVE_BY_ONE 3 if (MainGadList) FreeMainGadgets(); if (!MainVI) { MainVI = GetVisualInfoA(SnoopScreen, NULL); if (!MainVI) return (NULL); } font = MyOpenFont(gadgetfa); if (!font) return (NULL); BufferFont = MyOpenFont(bufferfa); if (!BufferFont) { CloseFont(font); return (NULL); } fonty = font->tf_YSize; bfontx = BufferFont->tf_XSize; bfonty = BufferFont->tf_YSize; bfontbaseline = BufferFont->tf_Baseline; if (SquareAspect) { VBorderGap = 2; gadg_gap = 3; } else { VBorderGap = 1; gadg_gap = 2; } gheight = fonty + GadgetHeight; spacing = gheight + gadg_gap; colwidth[MAIN_NARROW] = MaxTextLen(font, MainWidthLeftText) + 12; colwidth[MAIN_WIDE] = MaxTextLen(font, MainWidthRightText) + 12; /* * First, check if window is wide enough for gadgets. If not, * then fail. */ innerwidth = width - BorderLeft - bordright; if ((colwidth[MAIN_NARROW]+colwidth[MAIN_WIDE]+margin) * 2 > innerwidth) { CloseFont(font); CloseFont(BufferFont); BufferFont = NULL; return (NULL); } /* * Now calculate how many columns our fake listview can hold using * the current font, and use this to adjust our margins accordingly. * * The '4' in boxmaxwidth comes from 2 pixels for the left and right * of the box (the borders) */ boxmaxwidth = innerwidth - 2*BOX_LEFT_MARGIN - 4; BoxCols = (boxmaxwidth / bfontx); width4 = colwidth[MAIN_NARROW] * 2 + colwidth[MAIN_WIDE] * 2; width6 = colwidth[MAIN_NARROW] * 4 + colwidth[MAIN_WIDE] * 2; width8 = colwidth[MAIN_NARROW] * 4 + colwidth[MAIN_WIDE] * 4; width12 = colwidth[MAIN_NARROW] * 8 + colwidth[MAIN_WIDE] * 4; colpos[0] = BorderLeft + margin; innerwidth -= margin * 2; if (GadgetsLine) { if (statusline && width12 <= innerwidth) { /* * Setup layout for 12 x 1 grid */ int totalspace = innerwidth - width12; int i; for (i = 1; i < 8; i++) colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * i + (totalspace*i)/11; for (i = 8; i < 12; i++) colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * 8 + colwidth[MAIN_WIDE] * (i-8) + (totalspace*i)/11; /* * We always ensure that there is at least two pixels * between the end of the status line and the beginning * of the next gadget, otherwise we get a nasty artifact * where the identically-coloured white borders meet. */ colwidth[MAIN_STATUS] = colpos[3] - colpos[0] + colwidth[MAIN_NARROW]; if ((colpos[0] + colwidth[MAIN_STATUS] + 2) > colpos[4]) colwidth[MAIN_STATUS] = colpos[4] - colpos[0] - 2; gridtype = TWELVE_BY_ONE; numrows = 1; } else if (statusline && width6 <= innerwidth) { /* * Setup layout for 6 x 2 grid. */ int totalspace = innerwidth - width6; int i; for (i = 1; i < 4; i++) colpos[i] = colpos[0] + colwidth[MAIN_NARROW]*i + (totalspace*i)/5; colwidth[MAIN_STATUS] = colpos[3] - colpos[0] + colwidth[MAIN_NARROW]; colpos[5] = width - bordright - margin - colwidth[MAIN_WIDE]; colpos[4] = colpos[5] - colwidth[MAIN_WIDE] - totalspace/5; /* * If there isn't at least 2 pixels between the status line * and the adjacent wide gadget, reduce the size of each * wide gadget by 1 pixel (2 pixels in total) to prevent * a nasty clash between the two gadget edges. */ if ((colpos[0] + colwidth[MAIN_STATUS] + 2) > colpos[4]) { colpos[4] += 2; colpos[5]++; colwidth[MAIN_WIDE]--; } gridtype = SIX_BY_TWO; numrows = 2; } else if (!statusline && width8 <= innerwidth) { /* * Setup layout for 8 x 1 grid */ int totalspace = innerwidth - width8; int i; for (i = 1; i < 4; i++) colpos[i] = colpos[0] + colwidth[MAIN_NARROW]*i + (totalspace*i)/7; for (i = 4; i < 8; i++) colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * 4 + colwidth[MAIN_WIDE] * (i-4) + (totalspace*i)/7; gridtype = EIGHT_BY_ONE; numrows = 1; } else { /* * Setup layout for 4 x 3 grid */ int totalspace = (innerwidth - width4); colwidth[MAIN_STATUS] = width - bordright - margin - colpos[0]; colpos[3] = width - bordright - margin - colwidth[MAIN_WIDE]; colpos[1] = colpos[0] + colwidth[MAIN_NARROW] + totalspace/3; colpos[2] = colpos[3] - colwidth[MAIN_WIDE] - totalspace/3; gridtype = FOUR_BY_THREE; if (statusline) numrows = 3; else numrows = 2; } } else { /* * No gadget line, so set all the gadget positions to a * negative value to ensure the gadgets stay hidden, and * create or don't create a status line as appropriate. */ int i; colwidth[MAIN_STATUS] = innerwidth; gridtype = TWELVE_BY_ONE; numrows = 1; for (i = 1; i < 12; i++) colpos[i] = INVIS_LEFT_EDGE; /* Gadget off left-hand edge */ if (!statusline) { numrows = 0; colpos[0] = INVIS_LEFT_EDGE; } } colwidth[MAIN_NARROW_TOGGLE] = colwidth[MAIN_NARROW]; colwidth[MAIN_WIDE_INVIS] = colwidth[MAIN_WIDE]; /* * Next, calculate how many lines we can fit in the main buffer * display. The total vertical margin is fonty + 8 + BarHeight * pixels, which is distributed as follows: * * ----titlebar---- * <1 pixel border> * 4 pixel gap * Column headings * * 2 pixels of border * * Lines of text * * (1 pixel border) * * And in addition, if we have gadgets enabled: * * (1 pixel border) * * ---gadget area--- * * (1 pixel border) * ---bottom border--- * * The minimum spacing between gadgets is also gadg_gap. * Each individual gadget has a height 6 pixels higher than * the font size. * * The catch is to take the and distribute * it equally between the various gadget spaces etc. For N rows * of gadgets, this implies that there are (N+1) "gaps" that can * have vertical space added to them. */ BoxLeft = BorderLeft; BoxWidth = width - bordright - BoxLeft; BoxInLeft = BoxLeft + BOX_LEFT_MARGIN + 2; BoxInWidth = bfontx * BoxCols; BoxCharWidth = bfontx; BoxCharHeight = bfonty; yoffset = height - bordbot - spacing * numrows - gadg_gap; BoxSpacing = bfonty + BoxInterGap; if (SquareAspect) { BoxTop = TitlebarHeight + 6 + bfonty; BoxHeaderLine = TitlebarHeight + 4 + bfontbaseline; } else { BoxTop = TitlebarHeight + 5 + bfonty; BoxHeaderLine = TitlebarHeight + 3 + bfontbaseline; } if (numrows) boxheight = yoffset - gadg_gap - VBorderGap*3 - BoxTop-2; else boxheight = height - bordbot - BoxTop - 2*VBorderGap - 2; BoxRows = (boxheight + BoxInterGap) / BoxSpacing; if (numrows) BoxHeight = BoxRows * BoxSpacing - BoxInterGap + 2*VBorderGap + 2; else BoxHeight = height - bordbot - BoxTop; BoxLowest = BoxTop + BoxHeight + VBorderGap; BoxInTop = BoxTop + VBorderGap + 1; BoxBaseline = BoxInTop + bfontbaseline; BoxInHeight = BoxRows * BoxSpacing - BoxInterGap; InitMainMargins(); /* * Now get ready to create the button and status line gadgets * We always create the gadgets, even if they're not currently * being displayed. */ ng.ng_TextAttr = gadgetfa; ng.ng_VisualInfo = MainVI; ng.ng_Flags = PLACETEXT_IN; ng.ng_GadgetText = ""; ng.ng_GadgetID = 0; gadlist = NULL; gad = CreateContext(&gadlist); if (!gad) goto mgad_failed; /* * Now, the buttons and status line gadget. At this point, * we distribute any additional space that we have left between * the lower scroll bar and the current start of the gadget * grid (aka yoffset). */ extraspace = yoffset - (BoxTop + BoxHeight); yoffset -= extraspace - extraspace/(numrows+1) - ((extraspace % (numrows+1)) + 1)/2; spacing += extraspace / (numrows + 1); yoffset += gadg_gap; ng.ng_Height = gheight; if (gridtype == FOUR_BY_THREE && !statusline) yoffset -= spacing; /* Bias to allow for invisible status bar */ for (mg = &MainGadgs[0]; mg->gadgid; mg++) { switch (gridtype) { case FOUR_BY_THREE: ng.ng_LeftEdge = colpos[mg->col4]; ng.ng_TopEdge = yoffset + spacing * mg->row4; ng.ng_Width = colwidth[mg->widthtype]; break; case SIX_BY_TWO: ng.ng_LeftEdge = colpos[mg->col6]; ng.ng_TopEdge = yoffset + spacing * mg->row6; ng.ng_Width = colwidth[mg->widthtype]; break; case EIGHT_BY_ONE: ng.ng_LeftEdge = colpos[mg->col8]; ng.ng_TopEdge = yoffset; ng.ng_Width = colwidth[mg->widthtype]; break; case TWELVE_BY_ONE: ng.ng_LeftEdge = colpos[mg->col12]; ng.ng_TopEdge = yoffset; ng.ng_Width = colwidth[mg->widthtype]; break; } ng.ng_GadgetText = MSG(mg->stringid); ng.ng_GadgetID = mg->gadgid; if (mg->gadgid == GID_STATUS) { if (statusline) { int len = GetTextLen(font, MSG(mg->stringid)) + 9; ng.ng_LeftEdge += len; ng.ng_Width -= len; ng.ng_Flags = PLACETEXT_LEFT; gad = CreateGadget(TEXT_KIND, gad, &ng, GT_Underscore, '_', GTTX_Text, StatusLineText, GTTX_Border, TRUE, TAG_DONE); ng.ng_Flags = PLACETEXT_IN; if (!gad) goto mgad_failed; Gadget[GID_STATUS] = gad; } else { Gadget[GID_STATUS] = NULL; } } else { gad = CreateGadget(BUTTON_KIND, gad, &ng, GT_Underscore, '_', TAG_DONE); if (!gad) goto mgad_failed; if (mg->widthtype == MAIN_NARROW_TOGGLE) gad->Activation |= GACT_TOGGLESELECT; if (mg->widthtype == MAIN_WIDE_INVIS) gad->LeftEdge = INVIS_LEFT_EDGE; Gadget[mg->gadgid] = gad; } AddKeyShortcut(MainKeyboard, mg->gadgid, mg->stringid); } LogButtonLeft = Gadget[GID_OPENLOG]->LeftEdge; SetLogGadget(DefaultLogMode, LG_NOREFRESH); /* * Now some additional initialisation for the Pause * and Disable gadgets in case they're initially set */ if (Paused) Gadget[GID_PAUSE ]->Flags |= GFLG_SELECTED; if (Disabled) Gadget[GID_DISABLE]->Flags |= GFLG_SELECTED; /* * Now a quick check to see if we have opened commodities.library * yet -- if we haven't, then disable the hide gadget. */ if (CurSettings.Setup.HideMethod == HIDE_NONE) Gadget[GID_HIDE]->Flags |= GFLG_DISABLED; return (gadlist); mgad_failed: FreeMainGadgets(); CloseFont(font); return (NULL); } /* * RecalcMainWindow(width, height, refresh) * * Updates the gadgets for the main window to reflect the new size, * and renders the changes to the screen. If the refresh parameter * is REDRAW_GADGETS, then the function will automatically redraw * the gadgets in the window, otherwise this must be done by hand * (or perhaps automatically if Intuition generates an IDCMP_REFRESH * message after a resize event!) * * Returns FALSE if something went wrong (in which case the caller * is responsible for closing the main window) and displaying an * error message. */ BOOL RecalcMainWindow(int width, int height, int dorefresh) { struct RastPort *rport = MainWindow->RPort; int saveleft = BoxLeft; int savewidth = BoxWidth; int saveheight = BoxHeight; int oldrows = BoxRows; int startrow; if (!MainWindow) return (TRUE); if (MainGadList && !RemovedGadgets) RemoveGList(MainWindow, MainGadList, -1); MainGadList = CreateMainGadgets(CurMainGadgetFA, CurMainBufferFA, width, height, StatusLine); if (!MainGadList) return (FALSE); UpdateMainHScroll(); UpdateMainVScroll(); if (BoxWidth == savewidth && BoxLeft == saveleft && !ChangedSpacing) { if (BoxHeight == saveheight) /* * Only erase from bottom of scroll box down since the * rest hasn't actually changed at all. */ startrow = BoxLowest; else { startrow = BoxTop + MIN(BoxHeight, saveheight) - 1; startrow = MAX(startrow, TitlebarHeight); } } else startrow = TitlebarHeight; SetDrMd(rport, JAM1); SetAPen(rport, 0); AddGList(MainWindow, MainGadList, -1, -1, NULL); if (startrow == BoxLowest) { /* * If we are only refeshing the bottom part of the screen, * don't bother erasing the top half -- it looks much * cleaner. */ if (StatusLine || GadgetsLine) { // SetAPen(rport, 4); /* Makes gadget bground gray (nice!) */ RectFill(rport, BoxLeft + 2, BoxTop + BoxHeight + 1, BoxLeft + BoxWidth - 3, MainWindow->Height - MainWindow->BorderBottom - 1); DrawBevelBox(rport, BoxLeft, BoxTop + BoxHeight, BoxWidth, MainWindow->Height - MainWindow->BorderBottom - BoxTop - BoxHeight, GT_VisualInfo, MainVI, TAG_DONE); } if (dorefresh) RefreshGList(MainGadList, MainWindow, NULL, -1); } else { startrow = MAX(TitlebarHeight, startrow - BoxSpacing - 3); RectFill(rport, MainWindow->BorderLeft, startrow, width - MainWindow->BorderRight - 1, height - MainWindow->BorderBottom - 1); if (dorefresh) RefreshGList(MainGadList, MainWindow, NULL, -1); /* * If we are currently at the end of the display, we want to * ensure that we scroll to the end after refreshing the window. */ if (TopSeq + oldrows > EndSeq) { /* * Force redraw from buffer end after resize */ TopSeq = EndSeq; TopEvent = EndEvent; } RedrawMainWindow(); UpdateMainVScroll(); } GT_RefreshWindow(MainWindow, NULL); CurWindowWidth = MainWindow->Width; CurWindowHeight = MainWindow->Height; ChangedSpacing = 0; return (TRUE); } /* * SetMenuOptions() * * Configures the main window's menu strip to match the currently * selected options (e.g. Pause/Disable, Text Spacing, window type, * icon type, etc.) * * Call whenever settings change. * */ void SetMenuOptions(void) { struct MenuItem *item; #define SetMenuState(menunum, val) \ item = ItemAddress(MainWinMenu, menunum); \ item->Flags = (item->Flags & ~CHECKED) | ((val) ? CHECKED : 0) if (MainWindow && MainWinMenu) { int curpri = SysBase->ThisTask->tc_Node.ln_Pri; int i; SetMenuState(MENU_PAUSE, Paused); SetMenuState(MENU_DISABLE, Disabled); SetMenuState(MENU_AUTO_OPEN, AutoOpen); SetMenuState(MENU_DIS_HIDDEN, DisableOnHide); SetMenuState(MENU_STATUS, StatusLine); SetMenuState(MENU_GADGETS, GadgetsLine); SetMenuState(MENU_ICONS, CreateIcons); SetMenuState(MENU_SIMPLE, CurSettings.SimpleRefresh); SetMenuState(MENU_SMART, !CurSettings.SimpleRefresh); SetMenuState(MENU_AL_LEFT, !RightAligned); SetMenuState(MENU_AL_RIGHT, RightAligned); SetMenuState(MENU_SPACING0, BoxInterGap == 0); SetMenuState(MENU_SPACING1, BoxInterGap == 1); SetMenuState(MENU_SPACING2, BoxInterGap == 2); SetMenuState(MENU_ROWQUAL_ANY, RowQual == ROWQUAL_ANY); SetMenuState(MENU_ROWQUAL_NONE, RowQual == ROWQUAL_NONE); SetMenuState(MENU_ROWQUAL_SHIFT, RowQual == ROWQUAL_SHIFT); SetMenuState(MENU_ROWQUAL_ALT, RowQual == ROWQUAL_ALT); SetMenuState(MENU_ROWQUAL_CTRL, RowQual == ROWQUAL_CTRL); SetMenuState(MENU_ROWQUAL_ALL, RowQual == ROWQUAL_ALL); if (CurSettings.Setup.HideMethod == HIDE_NONE) OffMenu(MainWindow, MENU_HIDE); else OnMenu(MainWindow, MENU_HIDE); if (LogActive) { OffMenu(MainWindow, MENU_OPENLOG); OnMenu(MainWindow, MENU_CLOSELOG); } else { OnMenu(MainWindow, MENU_OPENLOG); OffMenu(MainWindow, MENU_CLOSELOG); } /* * We calculate our current priority on the fly. This * means we'll always be up-to-date, even if someone * changes our priority from outside the program. */ for (i = 0; i < MENU_NUMPRI; i++) { int subnum = MENU_CHANGEPRI | SHIFTSUB(i); struct MenuItem *item = ItemAddress(MainWinMenu, subnum); struct IntuiText *itext = (void *)(item->ItemFill); if (atoi(itext->IText) == curpri) item->Flags |= CHECKED; else item->Flags &= ~CHECKED; } } } /* * SetMainHideState(state) * * Updates the current main window titlebar, hide gadget, and * menu HIDE option to reflect the given hide state. * * It works as follows. If state is HIDE_NONE, then the titlebar * is set to and the gadget/menu item are ghosted. If * state is not HIDE_NONE, the gadget/menu item are unghosted, * and the titlebar is set to , unless HotKeyActive * is set to 0, in which case it is set to . */ void SetMainHideState(int hidestate) { char title[200]; char *keyname = CurSettings.Setup.HotKey; if (!HotKeyActive) keyname = MSG(MSG_INVALID_HOTKEY); if (hidestate == HIDE_NONE) strcpy(title, SnoopDosTitle); else mysprintf(title, SnoopDosTitleKey, keyname); if (strcmp(CurrentTitle, title) != 0) strcpy(CurrentTitle, title); if (MainWindow) { struct Gadget *gad = Gadget[GID_HIDE]; int oldstate = gad->Flags & GFLG_DISABLED; int gadpos; SetWindowTitles(MainWindow, CurrentTitle, (UBYTE *)-1); gadpos = RemoveGadget(MainWindow, gad); if (hidestate == HIDE_NONE) gad->Flags |= GFLG_DISABLED; else gad->Flags &= ~GFLG_DISABLED; AddGadget(MainWindow, gad, gadpos); if ((gad->Flags & GFLG_DISABLED) != oldstate) { /* * Only refresh the gadget if its state changed */ RefreshGList(gad, MainWindow, NULL, 1); } if (MainWinMenu) { if (CurSettings.Setup.HideMethod == HIDE_NONE) OffMenu(MainWindow, MENU_HIDE); else OnMenu(MainWindow, MENU_HIDE); } } } /* * OpenMainWindow() * * Opens the main window with the scroll buffer display, arrow gadgets, * and buttons. Also creates the status line gadget if necessary. * * Returns true for success, false for failure. */ BOOL OpenMainWindow(void) { static WORD initzoomdims[] = { 0, 0, -1, -1 }; struct Window *win; struct Gadget *scrollgadlist; struct TextAttr *gadgetfa; struct TextAttr *bufferfa; int minx, miny; int menupen; int i; int width = CurSettings.MainWinWidth; int height = CurSettings.MainWinHeight; int left = CurSettings.MainWinLeft; int top = CurSettings.MainWinTop; CheckSegTracker(); if (MainWindow) { WindowToFront(MainWindow); ActivateWindow(MainWindow); return (TRUE); } if (!CheckForScreen()) goto open_main_failed; for (i = 0; gadgetfa = MainWindowFontList[i].gadgetfa; i++) { bufferfa = MainWindowFontList[i].bufferfa; if (CalcMinMainSize(gadgetfa, bufferfa, &minx, &miny)) break; } if (!gadgetfa) goto open_main_failed; if (width == -1) width = DEF_WINDOW_WIDTH; if (height == -1) height = DEF_WINDOW_HEIGHT; if (width == 0) width = CurSettings.MainWinWidth; if (height == 0) height = CurSettings.MainWinHeight; if (width < minx) width = minx; if (height < miny) height = miny; if (width > ScreenWidth) width = ScreenWidth; if (height > ScreenHeight) height = ScreenHeight; if (left == -1) left = 0; if (top == -1) top = SnoopScreen->BarHeight + 2; initzoomdims[2] = ScreenWidth; initzoomdims[3] = ScreenHeight; ShowBuffer(TopSeq, DISPLAY_NONE); /* Ensure scroll vars are up to date */ MainGadList = CreateMainGadgets(gadgetfa, bufferfa, width, height, StatusLine); scrollgadlist = CreateScrollGadgets(); if (!scrollgadlist || !MainGadList) goto open_main_failed; Gadget[GID_ENDSCROLL]->NextGadget = MainGadList; CurMainGadgetFA = gadgetfa; CurMainBufferFA = bufferfa; SetMainHideState(CurSettings.Setup.HideMethod); /* Make title up to date */ win = OpenWindowTags(NULL, WA_Title, CurrentTitle, WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW | IDCMP_NEWSIZE | IDCMP_CHANGEWINDOW | IDCMP_RAWKEY | IDCMP_MENUPICK | IDCMP_IDCMPUPDATE | IDCMP_GADGETDOWN | IDCMP_MOUSEBUTTONS | IDCMP_MOUSEMOVE | IDCMP_MENUHELP | IDCMP_SIZEVERIFY | IDCMP_INACTIVEWINDOW | BUTTONIDCMP, WA_Left, left, WA_Top, top, WA_Width, width, WA_Height, height, WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_ACTIVATE | WFLG_SIZEGADGET | WFLG_SIZEBBOTTOM | WFLG_SIZEBRIGHT | WFLG_NEWLOOKMENUS, WA_MenuHelp, TRUE, RefreshTag, TRUE, WA_NoCareRefresh, NoCareRefreshBool, WA_PubScreen, SnoopScreen, WA_MinWidth, minx, WA_MinHeight, miny, WA_MaxWidth, -1, WA_MaxHeight, -1, WA_Zoom, initzoomdims, WA_Gadgets, scrollgadlist, TAG_DONE); if (!win) goto open_main_failed; MainWindow = win; MainWindowPort = win->UserPort; MainWindowMask = 1 << MainWindowPort->mp_SigBit; if (DraggingColumn || DraggingRow) { /* * If we were previously closed while a column was being dragged, * reset the drag flag and signal ourselves to ensure we catch * any events that arrived while output was suspended. */ DraggingColumn = 0; DraggingRow = 0; Signal(SysBase->ThisTask, NewEventMask); } RedrawMainWindow(); // RefreshGList(MainGadList, MainWindow, NULL, -1); /* Already done */ GT_RefreshWindow(MainWindow, NULL); UpdateStatus(); /* * Now create menus for window */ if (IntuitionBase->LibNode.lib_Version >= 39) menupen = 1; else menupen = 0; MainWinMenu = CreateMenus(MainMenu, GTMN_FrontPen, menupen, TAG_DONE); if (!MainWinMenu) ShowError(MSG(MSG_ERROR_CREATEMENUS)); else { LayoutMenus(MainWinMenu, MainVI, GTMN_NewLookMenus, TRUE, TAG_DONE); SetMenuStrip(MainWindow, MainWinMenu); SetMenuOptions(); } CurWindowWidth = MainWindow->Width; CurWindowHeight = MainWindow->Height; AwaitingResize = RESIZE_DONE; if ((SnoopScreen->Flags & SCREENTYPE) != WBENCHSCREEN) { /* * We're running on a custom screen, so redirect system * requesters to appear on this screen instead of the * Workbench screen. We don't do it unconditionally, because * our window title is so long that it makes the requesters * look very lopsided -- still, if we're on a custom screen, * it's better than nothing. */ *TaskWindowPtr = MainWindow; } if (DisableNestCount) DisableWindow(MainWindow, &MainRequester); return (TRUE); open_main_failed: ShowError(MSG(MSG_ERROR_OPENMAIN)); return (FALSE); } /* * CloseMainWindow(void) * * Closes the main SnoopDos window */ void CloseMainWindow(void) { *TaskWindowPtr = SaveWindowPtr; /* Restore previous screen address */ if (MainWindow) { if (MainWinMenu) { ClearMenuStrip(MainWindow); FreeMenus(MainWinMenu); MainWinMenu = NULL; } RecordWindowSizes(); CloseWindow(MainWindow); FreeMainGadgets(); MainWindow = NULL; MainWindowMask = 0; } FreeScrollGadgets(); if (MainVI) { FreeVisualInfo(MainVI); MainVI = NULL; } } /* * ShowStatus(msg) * * Displays the specified message in the status line (if enabled) */ void ShowStatus(char *msg) { strcpy(StatusLineText, msg); if (MainWindow) { GT_SetGadgetAttrs(Gadget[GID_STATUS], MainWindow, NULL, GTTX_Text, StatusLineText, TAG_DONE); } } /* * ReOpenMainWindow() * * Closes and re-opens main window retaining current size but * taking into account new font, spacing, refresh type, etc. * If another window is specified, then that window is brought * to the front after the main window is reopened. */ void ReOpenMainWindow(void) { if (MainWindow) { /* * If we've changed spacing but not yet updated the window to * reflect this, then if we are going from 0-spacing to 2-spacing, * the buffer may loose a few lines. To ensure we remain at the * end of the buffer if we were there to start with, we do an * invisible scroll to the _very_ end, just to be safe. */ if (BottomSeq >= EndSeq) { TopSeq = EndSeq; TopEvent = EndEvent; } CloseMainWindow(); OpenMainWindow(); } } /* * CheckForDirtyMainWindow() * * Checks to see if the main Window's rastport is dirty (i.e. we did * a scrollraster which was partially obscured by another window, and * now we need to refresh) and redraw the window if necessary. * * Typically called after ShowBuffer() */ void CheckForDirtyMainWindow(void) { if (MainWindow && MainWindow->RPort->Layer->Flags & LAYERREFRESH) { GT_BeginRefresh(MainWindow); RedrawMainWindow(); GT_EndRefresh(MainWindow, TRUE); } } /* * UpdateMainHScroll(void) * * Updates the main horizontal scrollbar to reflect the current * margin settings and buffer width. */ void UpdateMainHScroll(void) { if (MainWindow) SetGadgetAttrs(Gadget[GID_HSCROLLER], MainWindow, NULL, PGA_Top, LeftCol, PGA_Total, BufferWidth, PGA_Visible, BoxCols, TAG_DONE); } /* * UpdateMainVScroll(void) * * Updates the main scrollbar to reflect the current state of play. * N.b. Depends on the parameters calculated during the last call * to ShowBuffer. */ void UpdateMainVScroll(void) { if (MainWindow) SetGadgetAttrs(Gadget[GID_VSCROLLER], MainWindow, NULL, PGA_Top, TopSeq - FirstSeq, PGA_Total, EndSeq - FirstSeq + 1, PGA_Visible, BoxRows, TAG_DONE); } /* * ScrollHorizontal(amount) * * Scrolls the window horizontally to the left (-ve amount) or right * (+ve amount) and does the minimum amount of refresh necessary to * ensure the screen is correctly updated. i.e., we use ScrollRaster() * to perform the bulk of the scroll. */ void ScrollHorizontal(int amount) { struct RastPort *rport = MainWindow->RPort; int oldpos = LeftCol; Event *firstevent; int x0, y0, x1, y1; int h0, h1; int dx; int dxwidth; int saveboxleft; int saveleftcol; int saverightcol; int saveboxcols; LeftCol += amount; InitMainMargins(); if (!MainWindow || LeftCol == oldpos) return; UpdateMainHScroll(); /* * Okay, we're scrolling left or right, so do a ScrollRaster for * part of the amount and then use ShowBuffer() to fill in the * refreshed area. If there are no events or the buffer display * is now out of date, do a full refresh instead. * * We lock semaphores for the duration to ensure that the buffer * doesn't change under our feet which might lead to mismatched * lines of output. */ LOCK_LAYERS; ObtainSemaphore(&BufSem); dx = LeftCol - oldpos; dxwidth = dx * BoxCharWidth; firstevent = HeadNode(&EventList); if (!firstevent || TopSeq < firstevent->seqnum || abs(dx) > (BoxCols/2)) { RedrawMainWindow(); goto unlockall; } /* * Calculate co-ordinates for scroll */ h0 = BoxHeaderLine + BoxInTop - BoxBaseline; h1 = h0 + BoxCharHeight - 1; y0 = BoxInTop; y1 = BoxInTop + BoxInHeight - 1; x0 = BoxInLeft; x1 = BoxInLeft + (RightCol - LeftCol + 1) * BoxCharWidth - 1; if (GfxBase->LibNode.lib_Version >= 39) { /* * Do optimised flicker-free scroll under V39 */ struct Hook *oldhook; oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL); ScrollRasterBF(rport, dxwidth, 0, x0, h0, x1, y1); InstallLayerHook(MainWindow->WLayer, oldhook); } else { /* * Do a somewhat flickering scroll under V37 -- unfortunately, * we have to do the scroll in two separate parts, to avoid * erasing part of the separator bar between the header and * the buffer text when it is scrolled */ ScrollRaster(rport, dxwidth, 0, x0, h0, x1, h1); ScrollRaster(rport, dxwidth, 0, x0, y0, x1, y1); } /* * Finally, refresh the window section we just vacated by * temporarily changing the margins */ saveboxleft = BoxInLeft; saveleftcol = LeftCol; saverightcol = RightCol; saveboxcols = BoxCols; if (dx < 0) { /* * Scrolling to left so refresh left side of window */ if (RightCol > -dx) RightCol = LeftCol - dx - 1; BoxCols = RightCol - LeftCol + 1; } else { /* * Scrolling to right so refresh right side of window */ int offset = RightCol - LeftCol + 1 - dx; if (offset < 0) goto done_scroll; BoxInLeft += offset * BoxCharWidth; LeftCol += offset; BoxCols -= offset; } if (RightCol < LeftCol) goto done_scroll; DrawHeaderLine(); ShowBuffer(TopSeq, DISPLAY_ALL); done_scroll: BoxInLeft = saveboxleft; LeftCol = saveleftcol; RightCol = saverightcol; BoxCols = saveboxcols; /* * All done, now we can release semaphores and exit */ unlockall: ReleaseSemaphore(&BufSem); UNLOCK_LAYERS; CheckForDirtyMainWindow(); } /* * DoArrowScrolling() * * Scrolls window in the direction of the specified arrow * (GID_UPARROW, GID_DOWNARROW, GID_LEFTARROW or GID_RIGHTARROW) * and by the specified amount (usually 1 but sometimes more). */ void DoArrowScrolling(int arrowtype, int amount) { int changedvert = 0; int horizadjust = 0; int newseq; /* * If we're currently dragging columns, we ignore any attempts * at scrolling since it can cause problems with the format * being screwed up. */ if (DraggingColumn || DraggingRow) return; switch (arrowtype) { case GID_UPARROW: /* * Move up in a message */ if (TopSeq > FirstSeq) { changedvert = 1; newseq = TopSeq - amount; if (newseq < 1) newseq = 1; } break; case GID_DOWNARROW: /* We do a clever optimisation here for power users: * if SnoopDos is Paused and is at the end of the * buffer, then clicking on the down arrow (normally * a no-op) will have the same effect as selecting * single step. */ if (Paused && BottomSeq >= EndSeq) SingleStep(); changedvert = 1; newseq = TopSeq + amount; break; case GID_LEFTARROW: if (LeftCol > 0) horizadjust =- amount; break; case GID_RIGHTARROW: if (LeftCol + BoxCols <= BufferWidth) horizadjust = amount; break; } if (MainWindow) { if (horizadjust) ScrollHorizontal(horizadjust); if (changedvert) { InitMainMargins(); ShowBuffer(newseq, DISPLAY_QUICK); UpdateMainVScroll(); CheckForDirtyMainWindow(); } } } /* * InvertColumn(pos) * * Inverts the column drawn at the specified column position, relative * to the left margin of the box (i.e. 0 ... BoxCols-1) * * mode is INVERT_FULLBOX or INVERT_HEADER, depending on whether just * the top of the column or the entire column is to be inverted. This * allows for optimised updates if a column's position hasn't actually * changed but the header still needs to be redrawn. */ void InvertColumn(int col, int mode) { struct RastPort *rport = &InvertRP; int bfontbaseline = BufferFont->tf_Baseline; int y1 = BoxHeaderLine - bfontbaseline; int y2 = (mode == INVERT_HEADER ? (BoxTop - 1) : (BoxInTop + BoxInHeight)); if (col >= 0 && col < BoxCols) { int x = BoxInLeft + col * BoxCharWidth + BoxCharWidth / 2; RectFill(rport, x, y1, x+1, y2); } } /* * StartDragCol(x) * * Starts dragging a column displayed in the main window at absolute * coordinate X. * * Initialises all the variables associated with this and returns * with dragging enabled. */ void StartDragCol(int x) { EventFormat *ef; int clickcol; if (BufferEFormat[0].type == EF_END) /* Skip empty event formats */ return; /* * Now scan event buffer and figure out which heading we match * Note that the user must click on the event heading itself, * or it won't count (i.e. clicking on space between columns * doesn't work) -- this is to prevent confusion. */ clickcol = LeftCol + ((x - BoxInLeft) / BoxCharWidth); { /* * Select the column to drag */ int colpos = 0; int len = 0; for (ef = BufferEFormat; ef->type != EF_END; ef++) { if (clickcol >= colpos && clickcol < (colpos + ef->width + 1)) break; colpos += ef->width + 1; } /* * Now check if the clickcol should affect the left or right * sides of the selected column. The leftmost column is a * special case (in that we always choose the righthand column * rather the preceding lefthand, since you can't drag the * lefthand column any further to the left). * * In the normal case, if there is any blank area to the right of * the column, then clicking in the blank area activates the * the right column adjustment, else the left column adjustment. * If there is no blank area, then clicking in the rightmost * column activates the right column, else the left column. */ if (ef->type != EF_END) len = strlen(MSG(ef->titlemsgid)); if (ef == BufferEFormat || ef->type == EF_END || clickcol >= (colpos + MIN(ef->width-1, len))) { /* * Adjusting the righthand side of the selected column */ if (ef->type == EF_END) { DragEvent = ef-1; SelectEvent = ef-1; SelectStartCol = colpos - SelectEvent->width - 1; } else { SelectEvent = ef; SelectStartCol = colpos; DragEvent = ef; } DragOrigColWidth = DragEvent->width; NextOrigColWidth = (ef+1)->width; } else { /* * Adjusting the lefthand side of the selected column */ SelectEvent = ef; SelectStartCol = colpos; DragEvent = ef-1; DragOrigColWidth = DragEvent->width; NextOrigColWidth = ef->width; } } OrigLeftCol = LeftCol; OrigBufWidth = BufferWidth; ClickStartCol = clickcol; ClickCurCol = clickcol; DraggingColumn = 1; MovedColumn = 0; DrawHeaderLine(); /* * Now initialise the rastport we use for the inverted column * (we use a separate rastport for speed; otherwise, we have * to keep initialising it, which is rather slow.) */ InvertRP = *(MainWindow->RPort); if (GfxBase->LibNode.lib_Version >= 39) SetWriteMask(&InvertRP, 1); else InvertRP.Mask = 1; SetAPen(&InvertRP, 1); SetBPen(&InvertRP, 0); SetDrMd(&InvertRP, COMPLEMENT); InvertColumn(SelectStartCol-LeftCol-1, INVERT_FULLBOX); InvertColumn(SelectStartCol-LeftCol+SelectEvent->width, INVERT_FULLBOX); ReportMouse(TRUE, MainWindow); MainWindow->IDCMPFlags |= IDCMP_INTUITICKS; MainWindow->Flags |= WFLG_RMBTRAP; /* Need to detect RMB for cancel */ } /* * SizeColumn(eventptr, newwidth, movetype) * * Adjusts the width of the specified event entry to the new * value given, performing any scrolling and updating of the * main window that may be required. * * movetype is MOVECOL_FREE to allow free movement between the * columns to the left and right, or MOVECOL_FIXED to force * the columns to the right to move along with the column being * sized. */ void SizeColumn(EventFormat *ef, int newwidth, int movetype) { EventFormat *nextef = ef+1; int oldleftcol = LeftCol; int dwidth = newwidth - ef->width; int oldbufwidth = BufferWidth; int oldpos1 = SelectStartCol - LeftCol - 1; int oldpos2 = SelectStartCol - LeftCol + SelectEvent->width; int newpos1; int newpos2; int mode1; int mode2; if (dwidth == 0) return; if (movetype == MOVECOL_FREE && nextef->type != EF_END && (nextef+1)->type != EF_END) { /* * Bound movement so that we don't make the column to the * right of the main column exceed its bounds (in either * direction). */ int nextwidth = nextef->width - dwidth; if (nextwidth < 1) nextwidth = 1; if (nextwidth > MAX_FIELD_LEN) nextwidth = MAX_FIELD_LEN; if (nextwidth != (nextef->width - dwidth)) { dwidth = nextef->width - nextwidth; newwidth = ef->width + dwidth; } nextef->width = nextwidth; } else { /* * Dragging all the columns to the right of this one too so * update width to reflect the change */ BufferWidth += dwidth; } ef->width = newwidth; if (ef < SelectEvent) SelectStartCol += dwidth; ClickCurCol += dwidth; ClearMainRHS = 1; InitMainMargins(); /* * Check if the drag mouse position is off the right edge of the * window; if it is, then scroll the window to keep it in view. */ if (ClickCurCol >= (LeftCol + BoxCols)) { LeftCol = ClickCurCol - BoxCols + 1; InitMainMargins(); } if (BufferWidth != oldbufwidth || LeftCol != oldleftcol) UpdateMainHScroll(); newpos1 = SelectStartCol - LeftCol - 1; newpos2 = SelectStartCol - LeftCol + SelectEvent->width; mode1 = mode2 = INVERT_FULLBOX; if (newpos1 == oldpos1) mode1 = INVERT_HEADER; if (newpos2 == oldpos2) mode2 = INVERT_HEADER; InvertColumn(oldpos1, mode1); InvertColumn(oldpos2, mode2); DrawHeaderLine(); InvertColumn(newpos1, mode1); InvertColumn(newpos2, mode2); MovedColumn = 1; /* Show we updated a column for FinishDragCol() */ } /* * MoveColumn(xpos, movetype) * * Mini function used when dragging columns with the mouse -- * changes the width of the current column to pixel position xpos, * as much as possible. * * movetype is either MOVECOL_FREE or MOVECOL_FIXED -- see the * description of SizeColumn() for more details. */ void MoveColumn(int xpos, int movetype) { int colpos = LeftCol + ((xpos - BoxInLeft) / BoxCharWidth); if (DragEvent) { int disp = colpos - ClickStartCol; int newwidth = DragOrigColWidth + disp; int dwidth = newwidth - DragEvent->width; /* * Now check to see if the change we're making will * cause us to scroll off the left edge of the window. * If it will, then reduce the change so that we scroll * at a steady 1-character rate (this works out around * 10 chars a second, which should be fast enough). */ if (LeftCol > 0 && RightCol > (BufferWidth + dwidth)) { dwidth = RightCol - BufferWidth; newwidth = DragEvent->width + dwidth; } if (newwidth < 1) newwidth = 1; if (newwidth > MAX_FIELD_LEN) newwidth = MAX_FIELD_LEN; SizeColumn(DragEvent, newwidth, movetype); } } /* * FinishDragCol() * * Finishes up a drag operation (just resets a few variables) */ void FinishDragCol(void) { if (!DraggingColumn) return; ReportMouse(FALSE, MainWindow); MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS; MainWindow->Flags &= ~WFLG_RMBTRAP; /* Re-enable menus */ DraggingColumn = 0; /* * We can optimise our update a bit by checking MovedColumn. * This will only be true if the user actually resized one * of the columns, so it saves us having to do a redraw * if a column heading was accidentally clicked but not * moved. */ if (SelectEvent) { InvertColumn(SelectStartCol-LeftCol-1, INVERT_FULLBOX); InvertColumn(SelectStartCol-LeftCol+SelectEvent->width,INVERT_FULLBOX); } if (MovedColumn) { /* * Now update the settings window and format window with the * new format. This will also redraw the main window and * header line for us. */ BuildFormatString(BufferEFormat, BufFormat, MAX_FORM_LEN); InstallNewFormat(NEW_STRING); } else { /* * Nothing else to update, so just redraw the header line * (which will now be completely black again with no * highlighted item.) */ ClearMainRHS = 1; DrawHeaderLine(); } /* * Since we disable event scanning when columns are being dragged, * we signal ourselves to check for new events in case any * arrived while we were playing. */ Signal(SysBase->ThisTask, NewEventMask); } /* * StartDragRow(int y) * * Starts dragging a highlight at y (pixel row) in the event output. * This allows the user to use the mouse to highlight an entire line, * making it easy to see all the information on the line related to * the event. (This can sometimes be difficult to see in a normal * window when there is a lot of white space between columns). * * Initialises all the variables associated with this and returns * with dragging enabled. */ void StartDragRow(int y) { if (BufferEFormat[0].type == EF_END) /* Skip empty event formats */ return; SelectRow = 0; if (y > BoxInTop) SelectRow = (y - BoxInTop) / BoxSpacing; if (SelectRow >= BoxRows) SelectRow = BoxRows - 1; if ((TopSeq + SelectRow) > BottomSeq) SelectRow = BottomSeq - TopSeq; DraggingRow = 1; ReportMouse(TRUE, MainWindow); MainWindow->IDCMPFlags |= IDCMP_INTUITICKS; DrawSelectedLine(SelectRow, TRUE); } /* * MoveToRow(y) * * Moves the row highlight from its current position to a new position * if necessary (doesn't render anything if the row position remains * unchanged.) */ void MoveToRow(int y) { int newrow = 0; if (y > BoxInTop) newrow = (y - BoxInTop) / BoxSpacing; if (newrow >= BoxRows) newrow = BoxRows - 1; if ((TopSeq + newrow) > BottomSeq) newrow = BottomSeq - TopSeq; if (newrow != SelectRow) { DrawSelectedLine(SelectRow, FALSE); DrawSelectedLine(newrow, TRUE); SelectRow = newrow; } } /* * FinishDragRow() * * Stops highlighting the selected row and restores everything to normal */ void FinishDragRow(void) { if (!DraggingRow) return; DrawSelectedLine(SelectRow, FALSE); ReportMouse(FALSE, MainWindow); MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS; DraggingRow = 0; /* * It's possible that while we were playing with the row highlight, * the entire buffer may have been swept away under our feet, * leading to a slightly corrupt display (events out of sequence). * We don't mind this while we're dragging the highlight, but we want * to ensure it's sorted out properly afterwards. So, we do a quick * check to see if the current display is no longer valid, and if so, * then we redraw it. We need to handle new events first, else lots * of important internal variables will be out of sync. */ if (TopSeq < RealFirstSeq) { HandleNewEvents(); ShowBuffer(TopSeq, DISPLAY_ALL); } } /* * BeginResize() * * Removes gadget list from window and does a few other things * in preparation for a window resize occurring in the very * near future. */ void BeginResize(void) { if (!RemovedGadgets) RemoveGList(MainWindow, MainGadList, -1); RemovedGadgets = TRUE; if (BottomSeq >= EndSeq) AwaitingResize = RESIZE_BOTTOM; else AwaitingResize = RESIZE_MIDDLE; } /* * EndResize(void) * * Tiedies up after the window has been resized -- recalculates * gadget list, refreshes display, etc. * * Returns true for success, false for failure. */ int EndResize(void) { if (MainWindow->Width == CurWindowWidth && MainWindow->Height == CurWindowHeight) { /* * Window didn't change size after all, so don't make any * changes, just restore the status quo */ if (RemovedGadgets) AddGList(MainWindow, MainGadList, ~0, -1, NULL); AwaitingResize = RESIZE_DONE; } else { /* * Window was resized. If necessary, reposition the buffer * at the end of the new window (otherwise, if we were at * the end of the buffer and the window got smaller, there * would now be a few lines below the bottom of the window.) */ if (AwaitingResize == RESIZE_BOTTOM) TopSeq = EndSeq; AwaitingResize = RESIZE_DONE; if (!RecalcMainWindow(MainWindow->Width, MainWindow->Height, REDRAW_GADGETS)) { ShowError(MSG(MSG_ERROR_RESIZE)); return (0); } // RefreshWindowFrame(MainWindow); } RemovedGadgets = FALSE; /* * Now, since we disabled monitoring of events during * the resize, some may have accumulated that we missed. * So we generate a fake new events signal to force a * recheck in case anything did arrive. */ Signal(SysBase->ThisTask, NewEventMask); return (1); } /* * SetMainWindowWidth(int width) * * Sets the width of the main window to the specified number of * characters. Note that this call won't take effect immediately; * it just tells Intuition to resize our window, which will generate * a NEWSIZE event that we can trap. * * Note: has no effect if main window isn't open. */ void SetMainWindowWidth(int colwidth) { if (colwidth == 0) colwidth = BufferWidth; if (MainWindow) { int newleft = MainWindow->LeftEdge; int newtop = MainWindow->TopEdge; int newheight = MainWindow->Height; int newwidth = MainWindow->Width - BoxWidth + colwidth*BoxCharWidth + 2 * BOX_LEFT_MARGIN + 5; if (newwidth < MainWindow->MinWidth) newwidth = MainWindow->MinWidth; if ((newleft + newwidth) > ScreenWidth) newleft = ScreenWidth - newwidth; if (newleft < 0) { newleft = 0; newwidth = ScreenWidth; } /* * Now check if we're so close to the edge of the screen that * a single additional character space would push us over the * edge .. if so, then expand the width to the full screen * width, since it looks neater when the window completely * fills the screen, instead of "almost" filling it. */ if (newleft == 0 && (newwidth + BoxCharWidth) > ScreenWidth) newwidth = ScreenWidth; BeginResize(); ChangeWindowBox(MainWindow, newleft, newtop, newwidth, newheight); } } /* * HandleMainMsgs() * * Processes all outstanding messages associated with the main SnoopDos * window. To be called whenever we get a main window event. */ void HandleMainMsgs(void) { static int activegad; /* Non-zero if gadget is depressed */ static int activekey; /* Keycode shortcut of activegad */ struct IntuiMessage *imsg; Settings *newsettings = NULL; int loadlastsaved = 0; int donemain = 0; int origtopseq; int updateslider; int changedhoriz; int changedvert; int amount; ULONG newval; int refreshtype = CurSettings.SimpleRefresh; int newspacing = BoxInterGap; int newstatus = StatusLine; int newgadgets = GadgetsLine; int newmode = MonitorType; int newalign = RightAligned; int dohide = 0; if (!MainWindow) return; while ((imsg = GT_GetIMsg(MainWindowPort)) != NULL) { ULONG code = imsg->Code; int shift = imsg->Qualifier & IE_SHIFT; int altctrl = imsg->Qualifier & (IE_ALT | IE_CTRL); struct Gadget *gad; switch (imsg->Class) { case IDCMP_CLOSEWINDOW: if (CurSettings.Setup.HideMethod == HIDE_NONE) { donemain = 1; QuitFlag = 1; } else { dohide = 1; } break; case IDCMP_REFRESHWINDOW: /* * Since the events currently displayed in our buffer * window may have scrolled off the top of the buffer * in the meantime, if we just blindly refresh, we * might end up with the refreshed portions not matching * the rest of the display. So, first try and redraw the * buffer to ensure it contains current data. Normally, * this will end up being a NOP. * * See HandleNewEvents() for details as to why we need * to lock our layers first. */ DB("Begin Refresh Semapore\n"); origtopseq = LastDrawnTopSeq; LOCK_LAYERS; ObtainSemaphore(&BufSem); if (TopSeq != origtopseq) ShowBuffer(TopSeq, DISPLAY_ALL); GT_BeginRefresh(MainWindow); RedrawMainWindow(); GT_EndRefresh(MainWindow, TRUE); DB("End refresh semaphore\n"); ReleaseSemaphore(&BufSem); UNLOCK_LAYERS; if (TopSeq != origtopseq) UpdateMainVScroll(); break; case IDCMP_MENUHELP: /* * Display simple help for whatever menu was * active when the HELP key was pressed. To decide * which menu item to display help on, we simply * build a description string from the menu name * itself (replacing spaces with underscores and * skipping over any dots). * * We also strip out sub-menu items and just show * the help for the parent item instead. */ if (code != MENUNULL) { static int menunames[] = { MSG_LINK_MENU_PROJECT, MSG_LINK_MENU_WINDOW, MSG_LINK_MENU_SETTINGS, MSG_LINK_MENU_BUFFER }; char msgname[50]; char *msg; int menunum = MENUNUM(code); if (menunum == NOMENU) msg = MSG(MSG_LINK_MAINWIN); else if (ITEMNUM(code) == NOITEM) { msg = MSG(menunames[menunum]); } else { struct MenuItem *item; item = ItemAddress(MainWinMenu,code | SHIFTSUB(NOSUB)); if (!item || !(item->Flags & ITEMTEXT)) { msg = MSG(menunames[menunum]); } else { char *p; msg = ((struct IntuiText *)item->ItemFill)->IText; strcpy(msgname, "LINK Menu_"); p = msgname + strlen(msgname); while (*msg) { if (*msg == ' ' || *msg == '-') *p++ = '_'; else if (*msg != '.' && *msg != '?') *p++ = *msg; msg++; } *p = '\0'; msg = msgname; } } ShowAGuide(msg); } /* * It's possible the user may have played with some * menu toggles while the menu was displayed ... to * avoid us missing any items that were changed, we * simply reset them all back to the original state. */ SetMenuOptions(); break; case IDCMP_MENUPICK: while (code != MENUNULL) { struct MenuItem *item; int checked; item = ItemAddress(MainWinMenu, code); checked = (item->Flags & CHECKED) == CHECKED; switch ((ULONG)GTMENUITEM_USERDATA(item)) { case MID_OPENLOG: /* * Open a new logfile */ if (SelectFile(ChosenLogName, ChosenLogName, MainWindow, FILESEL_NEWLOGNAME)) { if (!OpenLog(LOGMODE_PROMPT, ChosenLogName)) ShowError(MSG(MSG_ERROR_STARTLOG), DefaultLogName); } break; case MID_CLOSELOG: CloseLog(); break; case MID_PAUSE: if (checked) newmode = MONITOR_PAUSED; else newmode = MONITOR_NORMAL; break; case MID_DISABLE: if (checked) newmode = MONITOR_DISABLED; else newmode = MONITOR_NORMAL; break; case MID_STEP: SingleStep(); newmode = MonitorType; break; case MID_CHANGEPRI: /* * New task priority selected -- let's * read the priority directly from the * menu item itself. */ if (checked) { struct IntuiText *itext; int newpri; itext = (void *)item->ItemFill; newpri = atoi(itext->IText); SetTaskPri(SysBase->ThisTask, newpri); } break; case MID_HELP: ShowAGuide(MSG(MSG_LINK_MAIN)); break; case MID_ABOUT: ShowError(MSG(MSG_ABOUT_SNOOPDOS), Version+6, PORT_NAME); break; case MID_HIDE: dohide = 1; break; case MID_QUIT: donemain = 1; QuitFlag = 1; break; /* * Windows menu */ case MID_SETUP: if (shift && SetWindow) CloseSettingsWindow(); else OpenSettingsWindow(); break; case MID_FUNCTION: if (shift && FuncWindow) CloseFunctionWindow(); else OpenFunctionWindow(); break; case MID_FORMAT: if (shift && FormWindow) CloseFormatWindow(); else OpenFormatWindow(); break; case MID_SETWIDTH: { struct IntuiText *itext = (void *)item->ItemFill; int newwidth = atoi(itext->IText); SetMainWindowWidth(newwidth); break; } case MID_ROWQUAL: { /* * Changing to a new row qualifier */ int newqual = SUBNUM(code); if (newqual != NOSUB) RowQual = newqual; break; } case MID_STATUS: newstatus = checked; break; case MID_GADGETS: newgadgets = checked; break; case MID_AUTO_OPEN: AutoOpen = checked; break; case MID_DISABLE_HIDDEN: DisableOnHide = checked;break; case MID_SPACE_NONE: if (checked) newspacing = 0; break; case MID_SPACE_1P: if (checked) newspacing = 1; break; case MID_SPACE_2P: if (checked) newspacing = 2; break; case MID_REF_SIMPLE: if (checked) refreshtype = 1; break; case MID_REF_SMART: if (checked) refreshtype = 0; break; case MID_ALIGN_LEFT: if (checked) newalign = 0; break; case MID_ALIGN_RIGHT: if (checked) newalign = 1; break; /* * Settings menu */ case MID_LOAD: if (SelectFile(ConfigFileName, ConfigFileName, MainWindow, FILESEL_LOADCONFIG)) { /* * This new settings file will becomoe * the last saved file, so just load * it in at the end instead */ newsettings = 0; loadlastsaved = 1; } break; case MID_SAVE: if (!SaveConfig(DefaultConfigName, SAVE_NOICON)) ShowError(MSG(MSG_ERROR_SAVING_SETTINGS), DefaultConfigName); break; case MID_SAVEAS: if (SelectFile(ConfigFileName, ConfigFileName, MainWindow, FILESEL_SAVECONFIG)) { if (!SaveConfig(ConfigFileName, SAVE_ICON)) { ShowError(MSG(MSG_ERROR_SAVING_SETTINGS), ConfigFileName); } } break; case MID_RESET: newsettings = &DefaultSettings; break; case MID_RESTORE: newsettings = &RestoreSettings; break; case MID_LASTSAVED: /* * If we successfully read in the defaults * file _or_ saved out a settings file, then * go ahead and read back in the last saved * file; otherwise, it's the very first * time we've ever been run by this user, * so just restore the factory settings * instead of producing an error message. */ if (GotLastSaved) { newsettings = 0; loadlastsaved = 1; } else { newsettings = &DefaultSettings; } break; case MID_ICONS: CreateIcons = checked; break; /* * Buffer menu */ case MID_COPYWIN: /* * Copy current window to clipboard */ DisableAllWindows(); if (!SaveBuffer(SAVEBUF_WINDOW, SAVEBUF_CLIPBOARD, SAVEBUF_OVERWRITE)) ShowError(MSG(MSG_ERROR_COPY_WIN_TO_CLIP)); EnableAllWindows(); break; case MID_COPYBUF: /* * Copy current buffer to clipboard */ DisableAllWindows(); if (!SaveBuffer(SAVEBUF_ALL, SAVEBUF_CLIPBOARD, SAVEBUF_OVERWRITE)) ShowError(MSG(MSG_ERROR_COPY_ALL_TO_CLIP)); EnableAllWindows(); break; case MID_SAVEWIN: if (SelectFile(BufferFileName, BufferFileName, MainWindow, FILESEL_SAVEWINDOW)) { DisableAllWindows(); if (!SaveBuffer(SAVEBUF_WINDOW, BufferFileName, SAVEBUF_PROMPT)) { ShowError(MSG(MSG_ERROR_SAVING_WINDOW), BufferFileName); } EnableAllWindows(); } break; case MID_SAVEBUF: if (SelectFile(BufferFileName, BufferFileName, MainWindow, FILESEL_SAVEBUFFER)) { DisableAllWindows(); if (!SaveBuffer(SAVEBUF_ALL, BufferFileName, SAVEBUF_PROMPT)) { ShowError(MSG(MSG_ERROR_SAVING_BUFFER), BufferFileName); } EnableAllWindows(); } break; case MID_CLEARBUF: ClearWindowBuffer(); break; } code = item->NextSelect; } break; case IDCMP_IDCMPUPDATE: /* * Handle BOOPSI slider movement. BOOPSI arrow * gadgets are no longer handled here but are * treated as ordinary button gadgets since * otherwise, we can't get the desired behaviour * (with a delay after the first click before it * starts repeating). */ changedhoriz = 0; changedvert = 0; updateslider = 0; GetAttr(PGA_Top, Gadget[GID_HSCROLLER], &newval); if (newval != LeftCol) ScrollHorizontal(newval - LeftCol); GetAttr(PGA_Top, Gadget[GID_VSCROLLER], &newval); if (newval != (TopSeq - FirstSeq)) { ShowBuffer(FirstSeq + newval, DISPLAY_QUICK); CheckForDirtyMainWindow(); } break; case IDCMP_INTUITICKS: if (ScrollDirection) { /* * Handle possible arrow gadget scrolling */ if (ScrollCount >= 3) { if (Gadget[ScrollDirection]->Flags & GFLG_SELECTED) DoArrowScrolling(ScrollDirection, 1); } else { ScrollCount++; } } if (DraggingColumn) { /* * Handle horizontal scrolling to left or right; * we just treat it as a normal mouse move -- in * the general case, the displacement will be * zero, so no updating will take place. */ MoveColumn(imsg->MouseX, shift ? MOVECOL_FIXED : MOVECOL_FREE); } if (DraggingRow) { /* * Dragging a highlight across rows, so we want to * automatically scroll if we are off the top or * bottom of the window. We also check to make sure * we're not off the top or bottom of the buffer * (no harm would come if we were, but it would * lead to unnecessary flashing of the display * highlight) */ int scrolldir = 0; if (imsg->MouseY < BoxInTop && TopSeq > FirstSeq) { scrolldir = GID_UPARROW; } else if (imsg->MouseY > (BoxInTop + BoxInHeight) && BottomSeq < EndSeq) { scrolldir = GID_DOWNARROW; } if (scrolldir) { FinishDragRow(); DoArrowScrolling(scrolldir, 1); StartDragRow(imsg->MouseY); } } break; case IDCMP_MOUSEBUTTONS: { static int lastrmb; int lmb = imsg->Qualifier & IEQUALIFIER_LEFTBUTTON; int rmb = imsg->Qualifier & IEQUALIFIER_RBUTTON; if (lmb && !rmb && !lastrmb) { static WORD lastx, lasty; static ULONG lastsecs, lastmicros; int curx = imsg->MouseX; int cury = imsg->MouseY; if (curx >= (lastx - 1) && curx <= (lastx + 1) && cury >= (lasty - 1) && cury <= (lasty + 1)) { if (DoubleClick(lastsecs, lastmicros, imsg->Seconds, imsg->Micros)) { /* * Got a double-click. Now check if it's * in the window header area. If so, * open or close the format window. */ if (cury < BoxTop) { if (shift && FormWindow) CloseFormatWindow(); else OpenFormatWindow(); break; } } } lastx = curx; lasty = cury; lastsecs = imsg->Seconds; lastmicros = imsg->Micros; } if (DraggingColumn) { if (lmb == 0) { /* * We've finished dragging our column */ FinishDragCol(); } else if (rmb) { /* * User clicked right button to cancel the * drag operation, so restore column width * to its original position and cancel drag. */ cancel_dragging_column: /* Stop highlight flash when restoring orig header */ if (DraggingColumn) { EventFormat *nextev = DragEvent + 1; /* * We invert the columns now and then * set SelectEvent to NULL to let * FinishDragCol() know we have already * unhighlighted them (this is because * we're hacking the window format * directly). */ InvertColumn(SelectStartCol - LeftCol - 1, INVERT_FULLBOX); InvertColumn(SelectStartCol - LeftCol + SelectEvent->width, INVERT_FULLBOX); SelectEvent = NULL; DragEvent->width = DragOrigColWidth; if (nextev->type != EF_END) nextev->width = NextOrigColWidth; BufferWidth = OrigBufWidth; if (LeftCol != OrigLeftCol) { LeftCol = OrigLeftCol; InitMainMargins(); UpdateMainHScroll(); } FinishDragCol(); } lastrmb = 1; break; } } else if (lmb && !lastrmb) { /* * Could be starting to drag a new column or row! * Check bounds to see if we are. If lastrmb was * true, then user has just released right mouse * button but hasn't yet released left mouse button, * so we ignore this attempt.. */ int x = imsg->MouseX; int y = imsg->MouseY; if (y < BoxTop && x >= BoxInLeft && x < (BoxInLeft + BoxInWidth)) { StartDragCol(x); } else if (y > BoxInTop && y < (BoxInTop + BoxInHeight)) { /* * Now check if correct qualifier is held down * to activate the row selection. We need to use * a slighty tricky boolean equation to handle * ROWQUAL_NONE, wher */ int qual = imsg->Qualifier & IE_ALL; if (RowQual == ROWQUAL_ANY || (RowQual == ROWQUAL_NONE && !qual) || (qual && (qual & RowQualTable[RowQual]) == qual)) { StartDragRow(y); } } } if (DraggingRow && !lmb) FinishDragRow(); lastrmb = rmb; break; } case IDCMP_MOUSEMOVE: if (DraggingColumn) { MoveColumn(imsg->MouseX, shift ? MOVECOL_FIXED : MOVECOL_FREE); } if (DraggingRow) MoveToRow(imsg->MouseY); break; case IDCMP_GADGETUP: case IDCMP_GADGETDOWN: gad = (struct Gadget *)imsg->IAddress; handle_maingadgets: switch (gad->GadgetID) { case GID_UPARROW: case GID_DOWNARROW: case GID_LEFTARROW: case GID_RIGHTARROW: if (imsg->Class == IDCMP_GADGETDOWN) { /* * User clicked on a scroll button so * kick-off the scrolling. */ ScrollDirection = gad->GadgetID; DoArrowScrolling(ScrollDirection, 1); MainWindow->IDCMPFlags |= IDCMP_INTUITICKS; ScrollCount = 0; } else { /* * User let go of a scroll button so turn * off scrolling. */ ScrollDirection = 0; MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS; } break; case GID_SETUP: if (shift && SetWindow) CloseSettingsWindow(); else OpenSettingsWindow(); break; case GID_FUNCTION: if (shift && FuncWindow) CloseFunctionWindow(); else OpenFunctionWindow(); break; case GID_SAVESET: /* * Save settings to configuration file */ ShowStatus(MSG(MSG_STATUS_SAVESET)); DisableAllWindows(); if (!SaveConfig(DefaultConfigName, SAVE_NOICON)) ShowError(MSG(MSG_ERROR_SAVING_SETTINGS), DefaultConfigName); EnableAllWindows(); UpdateStatus(); break; case GID_PAUSE: if (gad->Flags & GFLG_SELECTED) newmode = MONITOR_PAUSED; else newmode = MONITOR_NORMAL; break; case GID_DISABLE: if (gad->Flags & GFLG_SELECTED) newmode = MONITOR_DISABLED; else newmode = MONITOR_NORMAL; break; case GID_APPENDLOG: case GID_STARTLOG: case GID_SERIALLOG: if (!OpenLog(DefaultLogMode, DefaultLogName)) ShowError(MSG(MSG_ERROR_STARTLOG), DefaultLogName); break; case GID_OPENLOG: #if 0 /* * Quick and dirty benchmark code to scroll * the buffer 10 times to check speed */ { int i, l; for (i = 0; i < 10; i++) for (l = FirstSeq; l < EndSeq; l += BoxRows) ShowBuffer(l, DISPLAY_QUICK); break; } #endif /* * Back to normal: open a new log file */ if (SelectFile(ChosenLogName, ChosenLogName, MainWindow, FILESEL_NEWLOGNAME)) { OpenLog(LOGMODE_PROMPT, ChosenLogName); } break; case GID_CLOSELOG: CloseLog(); break; case GID_HIDE: dohide = 1; break; case GID_QUIT: donemain = 1; QuitFlag = 1; break; } break; case IDCMP_SIZEVERIFY: BeginResize(); break; case IDCMP_CHANGEWINDOW: case IDCMP_NEWSIZE: if (!EndResize()) donemain = 1; break; case IDCMP_INACTIVEWINDOW: /* * If window becomes inactive, cancel any column * drag currently in operation, since it could * confuse things otherwise when the window is * later re-activated. */ if (activegad) { ShowGadget(MainWindow, Gadget[activegad], GADGET_UP); activegad = 0; } if (DraggingRow) FinishDragRow(); if (DraggingColumn) goto cancel_dragging_column; break; case IDCMP_RAWKEY: { /* * Handle all keyboard shortcuts */ int upstroke = imsg->Code & 0x80; int keypress = ConvertIMsgToChar(imsg); UBYTE gadid = MainKeyboard[keypress]; if (activegad) { /* * We're releasing a gadget that was pressed * earlier, so handle it now */ int samekey = (imsg->Code & 0x7f) == activekey; if (samekey && !upstroke) /* Ignore repeated keys */ break; ShowGadget(MainWindow, Gadget[activegad], GADGET_UP); if (samekey) { /* * Just released the key that was originally * pressed for this gadget, so now go and * handle the gadget action. */ gadid = activegad; gad = Gadget[gadid]; activegad = 0; goto handle_maingadgets; } /* * If the above check didn't kick in, it means * we got a downpress of a different key, so * disable the gadget for this key and continue * to press the other key. */ activegad = 0; } /* * Handle simple keyboard input (i.e. vanilla keys) * * In addition to the gadget shortcuts in MainKeyboard[] * we also use TAB to unconditionally pause, and Space/ * Return to singlestep if we're already paused. * * (If we made Space always single step, then if someone * hit it by accident when the SnoopDos window was open, * it would go into pause mode which would be annoying.) */ if (gadid) { /* * Handle a gadget shortcut */ gad = Gadget[gadid]; if (gadid == GID_PAUSE || gadid == GID_DISABLE) { /* * PAUSE and DISABLE are toggle gadgets * which will highlight in the gadget code. * So, we handle them immediately, to give * instant feedback. */ gad->Flags ^= GFLG_SELECTED; goto handle_maingadgets; } else { /* * Now a slight problem ... since our logfile * gadget can cycle between multiple values, * we had better make sure that the same * shortcut will activate whichever one is * currently displayed rather than the * default hotkey (since all three opens * may share the same hotkey). If the logfile * is already open, we do nothing. */ if (gadid == GID_OPENLOG || gadid == GID_APPENDLOG || gadid == GID_STARTLOG || gadid == GID_SERIALLOG) { if (LogActive) break; gadid = LogButtonID; gad = Gadget[gadid]; } /* * All these gadget are button gadgets so we * just highlight them now, and set the * activegad flag -- on the upstroke, they'll * be released. */ ShowGadget(MainWindow, gad, GADGET_DOWN); activegad = gadid; activekey = imsg->Code; } break; } /* * Didn't match one of the gadget hotkeys, * now check for some other special keys */ switch (keypress) { case 0x0D: /* Return */ DoArrowScrolling(GID_DOWNARROW, 1); break; case ' ': /* Space */ if (DraggingColumn) { /* * Redraw window with current format * to date so user can see how it looks */ InvertColumn(SelectStartCol - LeftCol - 1, INVERT_FULLBOX); InvertColumn(SelectStartCol - LeftCol + SelectEvent->width, INVERT_FULLBOX); BuildFormatString(BufferEFormat, BufFormat, MAX_FORM_LEN); InstallNewFormat(NEW_STRING); InvertColumn(SelectStartCol - LeftCol - 1, INVERT_FULLBOX); InvertColumn(SelectStartCol - LeftCol + SelectEvent->width, INVERT_FULLBOX); } else if (MonitorType == MONITOR_PAUSED) { /* * Singlestep, but only if we were * already paused. */ SingleStep(); newmode = MonitorType; } break; case 0x09: /* Tab */ SingleStep(); newmode = MonitorType; break; case 0x03: /* CTRL-C -- quit */ gadid = GID_QUIT; ShowGadget(MainWindow, Gadget[gadid], GADGET_DOWN); activegad = gadid; activekey = imsg->Code; break; case 0x04: /* CTRL-D -- disable */ newmode = MONITOR_DISABLED; break; case 0x05: /* CTRL-E -- enable */ newmode = MONITOR_NORMAL; break; case 0x06: /* CTRL-F -- to front */ OpenMainWindow(); break; case '\033': /* ESC -- cancel column drag */ if (DraggingColumn) goto cancel_dragging_column; break; } /* * Finally, finally, we do a quick check to see * if the keystroke matches the format editor * menu shortcut; if it does, we open the format * editor. (If this key has been used by one of * the other gadgets, e.g. due to localisation, * then we will never get here, so not a problem.) * * As with the settings and functions windows, if * the keypress is shifted, then we close the * window if it was already open. */ if ((keypress & 0x5F) == (*MSG(MSG_WINDOWS_FORMAT) & 0x5F)) { if (shift && FormWindow) CloseFormatWindow(); else OpenFormatWindow(); break; } /* * Handle cursor keys etc. We allow scrolling by * varying numbers of steps, depending on the * qualifier key used. */ amount = 1; switch (imsg->Code) { case CURSORUP: if (shift) amount = BoxRows; if (altctrl) amount = TopSeq - FirstSeq; DoArrowScrolling(GID_UPARROW, amount); break; case CURSORDOWN: if (shift) amount = BoxRows; if (altctrl) amount = EndSeq - TopSeq; DoArrowScrolling(GID_DOWNARROW, amount); break; case CURSORLEFT: if (shift) amount = HSCROLL_SHIFT_JUMP; if (altctrl) amount = LeftCol; DoArrowScrolling(GID_LEFTARROW, amount); break; case CURSORRIGHT: if (shift) amount = HSCROLL_SHIFT_JUMP; if (altctrl) amount = BufferWidth - LeftCol; DoArrowScrolling(GID_RIGHTARROW, amount); break; case TABKEY: if (shift) newmode = MONITOR_NORMAL; break; case HELPKEY: ShowAGuide(MSG(MSG_LINK_MAINWIN)); break; } break; } } GT_ReplyIMsg(imsg); } if (newmode != MonitorType) SetMonitorMode(newmode); if (dohide) HideSnoopDos(); else if (donemain) CloseMainWindow(); else if (refreshtype != CurSettings.SimpleRefresh) { BoxInterGap = newspacing; StatusLine = newstatus; GadgetsLine = newgadgets; CurSettings.SimpleRefresh = refreshtype; ReOpenMainWindow(); } else if (newsettings) { InstallSettings(newsettings, SET_ALL); } else if (loadlastsaved) { /* * We read the last-saved configuration from a file rather * than just from a memory copy so that the user can edit * the disk version by hand first if they so choose */ if (!LoadConfig(ConfigFileName, MODE_INTERNAL, NULL)) ShowError(MSG(MSG_ERROR_LOADING_SETTINGS), ConfigFileName); } else { if (newspacing != BoxInterGap) SetTextSpacing(newspacing); else if (newalign != RightAligned) { RightAligned = newalign; ShowBuffer(TopSeq, DISPLAY_ALL); } if (newgadgets != GadgetsLine || newstatus != StatusLine) { StatusLine = newstatus; GadgetsLine = newgadgets; if (!RecalcMainWindow(MainWindow->Width, MainWindow->Height, REDRAW_GADGETS)) ShowError(MSG(MSG_ERROR_RESIZE)); } } } /* * DrawSelectedLine(row, highlight) * * Calculates what event is currently positioned at the specified row * in the main window and redraws that row either highlighted or * unhighlighted. If the event is now complete, its state is updated * accordingly. If no event can be found at that line, then the nearest * row is highlighted instead. * * If highlight is true, the row is highlighted, else it is unhighlighted. */ void DrawSelectedLine(int row, int highlight) { struct RastPort *rport = MainWindow->RPort; Event *ev; Event *firstevent; int firstseq; int fillpen = 0; int textpen = 1; int i; if (IsListEmpty(&EventList) || BufferEFormat[0].type == EF_END) return; /* * First, find the event */ LOCK_LAYERS; ObtainSemaphore(&BufSem); firstevent = HeadNode(&EventList); firstseq = firstevent->seqnum; ev = TopEvent; if (TopSeq < firstseq) ev = firstevent; for (i = 0; i < row && ev != TailNode(&EventList); i++, ev = NextNode(ev)) ; if (ev->status == ES_READY) ev->status = ES_ACCEPTED; FormatEvent(BufferEFormat, ev, BufferLine, LeftCol, LeftCol + BoxCols - 1); /* * Now setup rastport according to whether or not we want it * highlighted */ if (highlight) { fillpen = ScreenDI->dri_Pens[FILLPEN]; textpen = ScreenDI->dri_Pens[FILLTEXTPEN]; } SetAPen(rport, textpen); SetBPen(rport, fillpen); SetDrMd(rport, JAM2); Move(rport, BoxInLeft, BoxBaseline + i * BoxSpacing); Text(rport, BufferLine, BoxCols); ReleaseSemaphore(&BufSem); UNLOCK_LAYERS; } /* * OutputBufLine(event, row) * * Outputs the specified event to the buffer display at the given * row, using the current format, left and right col indicators. * See also DrawSelectedLine() above. */ void OutputBufLine(Event *event, int row) { struct RastPort *rport = MainWindow->RPort; if (event->status == ES_READY) event->status = ES_ACCEPTED; FormatEvent(BufferEFormat, event, BufferLine, LeftCol, RightCol); Move(rport, BoxInLeft, BoxBaseline + row*BoxSpacing); Text(rport, BufferLine, RightCol - LeftCol + 1); } /* * ClearWindowBuffer() * * Clears the buffer area on the screen, and empties the internal * buffer as well (called by the Clear Buffer menu option). */ void ClearWindowBuffer(void) { ClearBuffer(); if (MainWindow) { struct RastPort *rport = MainWindow->RPort; /* * Erase strip to right of our rendered text */ SetAPen(rport, 0); SetDrMd(rport, JAM1); RectFill(rport, BoxInLeft, BoxInTop, BoxInLeft+BoxInWidth-1, BoxInTop+BoxInHeight-1); UpdateMainVScroll(); } ClearMainRHS = 0; } /* * ShowBuffer(seqnum, displaytype) * * Adjusts the buffer display so that it begins at event numbered * seqnum. If more than half of the needed events are currently * on display, the buffer is scrolled to put them in the correct * position. Any lines not currently on display are redrawn. * * Updates TopSeq and BottomSeq accordingly. * * Also does validity checking to ensure that if our current set of * events has scrolled off the top of the buffer, we will still be * displayed. * * Displaytype is DISPLAY_ALL or DISPLAY_QUICK, to force either * the entire buffer to be redrawn dumbly at the new position, or * to optimise it by scrolling, only drawing changed items, etc. * * If displaytype is DISPLAY_NONE, then all the Seq variables are * recalculated to take account of events scrolling off the top * etc. but no new output is displayed. This allows us to update * the scrollbar without interrupting the current screen display * while the user is reading it. * * Note: it is save to call ShowBuffer with DISPLAY_NONE when the * main window is closed, but for all the others, it should be open. */ void ShowBuffer(LONG seqnum, int displaytype) { struct RastPort *rport; LONG oldtopseq = TopSeq; /* Saved old top position */ LONG firstseq; /* First sequence number on list */ Event *firstevent; /* First event on list */ Event *topev; /* New top event displayed on screen */ int row = 0; /* Current output row in buffer */ int scrollthresh; /* If moving less than this, use scroll */ /* * If there is a lot of activity, then sometimes we can get * output messages in between an IDCMP_VERIFY and an IDCMP_NEWSIZE * which can lead to screen artefacts appearing in the border. * One way to avoid these is to always refresh the borders when * we get an IDCMP_NEWSIZE, but a neater way is to suspend output * while we're between the two types of message -- this is less * visually jarring on the user. */ if (AwaitingResize != RESIZE_DONE) displaytype = DISPLAY_NONE; /* * Important that we lock our layers -- see HandleNewEvents() * for a more detailed explanation of why. */ DB("ShowBuffer-Semaphore\n"); LOCK_LAYERS; ObtainSemaphore(&BufSem); if (IsListEmpty(&EventList) || ((Event *)HeadNode(&EventList))->status == ES_CREATING) goto done_show_buffer; firstevent = HeadNode(&EventList); firstseq = firstevent->seqnum; if (TopSeq < firstseq) { TopEvent = firstevent; TopSeq = firstseq; BottomSeq = firstseq; /* * The events currently on display in the window must have * scrolled off the top of the buffer, so we can't assume * any of them still exist. So, force a full redraw instead. */ if (displaytype == DISPLAY_QUICK) displaytype = DISPLAY_ALL; } if (EndSeq < firstseq) { EndSeq = firstseq; EndEvent = firstevent; } if (seqnum < firstseq) seqnum = firstseq; if (seqnum > EndSeq) seqnum = EndSeq; /* * Work out if we need to scan forwards or backwards from the * current event at the top of the screen */ if (seqnum <= TopSeq) { /* * Scanning backwards from current top of screen. Now see if * it would be faster to scan forward from the start of the * list. */ if ((TopSeq - seqnum) < (seqnum - firstseq)) { for (topev = TopEvent; topev != HeadNode(&EventList) && topev->seqnum > seqnum; topev = PrevNode(topev)) ; } else { for (topev = firstevent; topev != TailNode(&EventList) && topev->seqnum < seqnum; topev = NextNode(topev)) ; } } else { /* seqnum > TopSeq */ /* * Scanning forwards from the current top of the screen. Now * see if it would be faster to scan backwards from the end of * the event list. */ if ((EndSeq - seqnum) < (seqnum - TopSeq)) { for (topev = EndEvent; topev != TopEvent && topev->seqnum > seqnum; topev = PrevNode(topev)) ; } else { for (topev = TopEvent; topev != EndEvent && topev->seqnum < seqnum; topev = NextNode(topev)) ; } } /* * Now topev is the new top event to display in the window. * But wait! What if this topev is close to the bottom of our * list? We want to always show a full window of text if * possible. So, adjust it to ensure as many events as possible * can fit in the screen. */ while (topev != firstevent && seqnum > (EndSeq - BoxRows + 1)) { topev = PrevNode(topev); seqnum--; } /* * Okay! Now we finally have topev pointing to our new top of * screen node. Now update our display accordingly. We also * take this opportunity to refresh our scroll bar, since it's * relative position may well now be different. */ FirstSeq = firstseq; TopEvent = topev; TopSeq = seqnum; if ((displaytype & DISPLAY_NONE) || !MainWindow) goto done_show_buffer; SetupBufferRastPort(); rport = MainWindow->RPort; /* * We record the top line that was last drawn into the buffer. This * is useful when ShowBuffer(..., DISPLAY_NONE) is called since it * let's us detect when the info inside the window is no longer valid * due to having scrolled off the top of the buffer. */ if (seqnum != oldtopseq) LastDrawnTopSeq = TopSeq; if (displaytype & DISPLAY_ALL) goto redrawall; /* * Setup so we only render to bitplane 0 -- this greatly * speeds up text output and rendering speed, especially * on deep Workbenches. */ if (GfxBase->LibNode.lib_Version >= 39) SetWriteMask(rport, 1); /* Speed up scrolling and rendering! */ else rport->Mask = 1; /* * Now setup our scroll threshold. If running on V37 (where * when we scroll, we get backfilled with colour 0 before * we can redraw), then our threshold is 1/2 the displayed * number of rows. If running on V39 or above, we increase * the threshold to 3/4 of the displayed rows. */ scrollthresh = BoxRows / 2; if (GfxBase->LibNode.lib_Version >= 39) scrollthresh = (BoxRows * 3) / 4; if (seqnum == oldtopseq) { /* * We're already at the correct position in the buffer, so * just scan looking for new or updated entries (i.e. any * which are not yet ES_ACCEPTED) */ while (row < BoxRows && seqnum <= EndSeq) { if (topev->status != ES_ACCEPTED) OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } } else if (seqnum > oldtopseq && (seqnum - oldtopseq) <= scrollthresh) { /* * Scrolling forward, so move buffer up. * * If running on V39 or above, we disable background fills * for our layer while we ScrollRaster() since we're going * to be redrawing the area anyway -- it speeds things up, * and is also less visually obtrusive. */ int dy = seqnum - oldtopseq; int scrollwidth = (RightCol - LeftCol + 1) * BoxCharWidth; DB("ScrollRaster start\n"); if (GfxBase->LibNode.lib_Version >= 39) { struct Hook *oldhook; oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL); ScrollRasterBF(rport, 0, dy * BoxSpacing, BoxInLeft, BoxInTop, BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1); InstallLayerHook(MainWindow->WLayer, oldhook); } else { ScrollRaster(rport, 0, dy * BoxSpacing, BoxInLeft, BoxInTop, BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1); } DB("ScrollRaster end\n"); while (seqnum <= EndSeq && row < (BoxRows - dy)) { /* * Even though these lines are already present, * we still have to re-output any lines that are * not ES_ACCEPTED since they may have been * updated since the last time they were displayed. */ if (topev->status != ES_ACCEPTED) OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } while (seqnum <= EndSeq && row < BoxRows) { OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } } else if (oldtopseq > seqnum && (oldtopseq - seqnum) <= scrollthresh) { /* * Scrolling backward, so move buffer down. */ int dy = oldtopseq - seqnum; int scrollwidth = (RightCol - LeftCol + 1) * BoxCharWidth; DB("ScrollRaster start\n"); if (GfxBase->LibNode.lib_Version >= 39) { struct Hook *oldhook; oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL); ScrollRasterBF(rport, 0, -dy * BoxSpacing, BoxInLeft, BoxInTop, BoxInLeft + scrollwidth-1, BoxInTop+BoxInHeight-1); InstallLayerHook(MainWindow->WLayer, oldhook); } else { ScrollRaster(rport, 0, -dy * BoxSpacing, BoxInLeft, BoxInTop, BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1); } DB("ScrollRaster end\n"); /* * Update the new (blank) areas first */ while (seqnum <= oldtopseq) { OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } /* * Now can the remaining lines in the buffer (which are * already on the screen) and re-output any which are * not ES_ACCEPTED, since they may have changed since the * last update. */ while (row < BoxRows && seqnum <= EndSeq) { if (topev->status != ES_ACCEPTED) OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } } else { /* * The move is too big to handle by scrolling, so just * redraw the entire buffer instead. */ redrawall: while (row < BoxRows && seqnum <= EndSeq) { OutputBufLine(topev, row); topev = NextNode(topev); seqnum++; row++; } } if ((displaytype & DISPLAY_ALL) == 0) { /* * Undo the bitplane optimisation we did earlier, but only * if we're doing an optimised refresh. */ if (GfxBase->LibNode.lib_Version >= 39) SetWriteMask(rport, 0xFF); else rport->Mask = 0xff; } BottomSeq = seqnum - 1; /* Bottom most sequence number in window */ done_show_buffer: DB("ShowBuffer-Semaphore-done\n"); ReleaseSemaphore(&BufSem); UNLOCK_LAYERS; } /* * HandleNewEvents() * * Called whenever we are signalled that a new event has arrived * in the input queue. * * We simply scan the queue from our last known position to our * current position and, if necessary, update our output window * to reflect any new events. We also output any new _complete_ * events to the logfile. */ void HandleNewEvents(void) { Event *firstevent; Event *newev; Event *ev; int curendcompleteseq = EndCompleteSeq; int curendseq = EndSeq; LONG newseq; int refresh = 0; DB("HandleNewEvent-Semaphore\n"); if (IsListEmpty(&EventList) || AwaitingResize != RESIZE_DONE || DraggingColumn || DraggingRow) return; /* * It is vital that for the duration of the screen update, * we retain complete access to our output rastport. Otherwise, * we can run into a deadlock where a higher priority task * (like input.device) locks the screen layer's while we * are in the middle of our refresh, and then calls one of * our patched functions (like OpenFont) -- instant hang * (because we own the buffer semaphore and the patched * function will sit waiting for us to free it). * * By locking our layers, we ensure that nobody else can * ever get in once we start our refresh. The downside is * that this locks out menu rendering etc, but since we'll * be quick at refreshing, that's okay. * * Note that we lock the layers _before_ getting the buffer * semaphore -- if we do it afterwards, there is a window of * opportunity where we task switch to a higher priority task * between the two calls which then proceeds to lock layers * and try and lock the buffer semaphore -- deadlock again! * * News Flash! We don't lock layers any more, since it lead * to jerky performance when dragging the scrollbar etc. * Instead, our patch code checks to see if our layers are * locked by the calling task and if they are, bypasses * outputting the event completely. Since the only things * that ever call our patches when layers are locked are * low-level Intuition functions that are re-rendering * gadgets etc (and calling OpenFont()) this is actually * a win-win since we're typically not interested in those * events anyway. */ if (MainWindow) { LOCK_LAYERS; /* This is now a no-op */ } ObtainSemaphore(&BufSem); if (EndSeq == BottomSeq) /* If at end of buf, show new o/p */ refresh = 1; firstevent = HeadNode(&EventList); if (firstevent->seqnum > EndCompleteSeq) newev = firstevent; else if (EndCompleteEvent == TailNode(&EventList)) { newev = EndCompleteEvent; } else { newev = NextNode(EndCompleteEvent); } newseq = newev->seqnum; /* * First we scan all the new incoming entries to figure out * our new EndEvent/EndSeq (for ShowBuffer) */ for (ev = newev; NextNode(ev) != NULL && ev->status != ES_CREATING; ev = NextNode(ev)) ; EndEvent = PrevNode(ev); EndSeq = EndEvent->seqnum; if (MainWindow) { if (refresh) { /* * We were already displaying the end of the buffer and we * got new info in, so move the display to the new end of * buffer. */ ShowBuffer(EndSeq, DISPLAY_QUICK); } else if (BottomSeq > EndCompleteSeq) { /* * We weren't at the very end of the buffer, but there was an * unfinished event displayed somewhere on the screen, so * rescan the currently displayed bufferlines in case it * needs to be updated. */ ShowBuffer(TopSeq, DISPLAY_QUICK); } else { /* * We're too far up in the buffer to show any of the * new data, but update our internal variables anyway * so we can keep the scroll bar up to date. */ ShowBuffer(TopSeq, DISPLAY_NONE); } } else { /* * Simply update TopSeq accordingly so it will be correct the * next time we open the window */ TopSeq = EndSeq; TopEvent = EndEvent; } /* * Now mark all new events which are finished as complete, so that * we won't rescan them */ for (ev = newev; NextNode(ev) != NULL && ev->status >= ES_READY; ev = NextNode(ev)) { EndCompleteEvent = ev; EndCompleteSeq = ev->seqnum; } DB("HandleNewEvent-Semaphore-done\n"); ReleaseSemaphore(&BufSem); if (MainWindow) { CheckForDirtyMainWindow(); UNLOCK_LAYERS; UpdateMainVScroll(); } if (LogActive && !Disabled) { /* * Now output the new events to our logfile. We have to be * very careful doing this since we must not keep the buffer * semaphore locked while doing file output (it could lead * to deadlocks if the file output causes a requester to * appear on the screen, for example). Thus, we must lock * and unlock the semaphore as we scan each event. Sigh. */ LONG latestready; /* seqnum of highest event that's ES_READY */ /* * Output the new events to the logfile. We only output * events that are complete, or events that are incomplete * but which have a complete event after them. Any complete * events that have been output earlier are not output * again, unless they were only partially output the first * time. */ ObtainSemaphore(&BufSem); ev = HeadNode(&EventList); if (ev->seqnum > newseq) { newev = ev; newseq = ev->seqnum; } else if (newseq == curendcompleteseq) { /* * We couldn't advance past the endcompleteseq earlier on * because there was nothing beyond it, so step past now. */ newev = NextNode(newev); newseq++; } for (ev = TailNode(&EventList); ev->seqnum > newseq && ev->status < ES_READY; ev = PrevNode(ev)) ; latestready = ev->seqnum; if (ev->status < ES_READY) latestready--; /* Just in case ev->seqnum == newseq */ for (ev = newev; NextNode(ev) != NULL && ev->seqnum <= latestready; ev = NextNode(ev)) { int firstchar = 0; /* * For each event that is either complete, or partially * complete but followed by a complete event, output it * to the logfile (unless already output). */ if (ev->status >= ES_READY) { if ((ev->flags & EFLG_LOGDONE) == 0) { if (ev->flags & EFLG_LOGPARTIAL) firstchar = '\\'; else firstchar = ' '; ev->flags |= EFLG_LOGDONE; } } else if ((ev->flags & EFLG_LOGPARTIAL) == 0) { /* * Output partial event */ ev->flags |= EFLG_LOGPARTIAL; firstchar = '/'; } if (firstchar) { /* * Output the line */ FormatEvent(LogEFormat, ev, BufferLine+1, 0, LogWidth - 1); BufferLine[0] = firstchar; BufferLine[LogWidth+1] = '\n'; BufferLine[LogWidth+2] = 0; ReleaseSemaphore(&BufSem); WriteLog(BufferLine); ObtainSemaphore(&BufSem); /* * While we had the semaphore released, it's possible * that the event we're on may have scrolled off the * buffer. If it has, then give up right now (we'll * catch the new ones the next time around). */ if (((Event *)HeadNode(&EventList))->seqnum > latestready) break; } } ReleaseSemaphore(&BufSem); if (Paused) WriteLog(NULL); /* Flush log if we're single-stepping */ } if (AutoOpen && !MainWindow && curendseq < EndSeq) ShowSnoopDos(); } /* * CleanupMainWindow() * * Frees any resources associated with this module */ void CleanupMainWindow(void) { CloseMainWindow(); if (MainGadList) FreeGadgets(MainGadList), MainGadList = NULL; if (BufferFont) CloseFont(BufferFont), BufferFont = NULL; if (LogActive) CloseLog(); }