home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Format CD 10
/
amigaformatcd10.iso
/
-look_here_1st!-
/
handy_tools
/
snoopdos
/
snoopdos_source
/
mainwin.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-09-17
|
148KB
|
5,220 lines
/*
* 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
* <VBorderGap>
* 2 pixels of border
* <VBorderGap>
* Lines of text
* <VBorderGap>
* (1 pixel border)
*
* And in addition, if we have gadgets enabled:
*
* (1 pixel border)
* <gadg_gap>
* ---gadget area---
* <gadg_gap>
* (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 <any additional space> 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 <none> 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 <key sequence>, unless HotKeyActive
* is set to 0, in which case it is set to <invalid>.
*/
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();
}