/* interface.c */ /* the actual interface to the player routine */ /* $Author: Espie $ * $Date: 91/05/14 02:29:48 $ * $Revision: 1.25 $ * $Log: interface.c,v $ * Revision 1.25 91/05/14 02:29:48 Espie * The auto resize was no longer working... Latest * version hadn't been tested. Put the code in order. * Added a temporary fix to the asynchronous resize * problem (window jumping all other the place when * doing fast resizes). * * I should rewrite part of the code to get rid of * relative coordinates once and for all (maybe...) * * Revision 1.24 91/05/12 19:53:21 Espie * pause handled like other gadgets, no longer special case. * * Revision 1.23 91/05/12 15:57:15 Espie * Minor changes. * Did not allocate enough points 2*number of point = coordinates, * so it did abort mysteriously, only when optimized.. sigh. * * Revision 1.22 91/05/10 00:03:55 Espie * * Corrected NewPropInfo bug. * * Revision 1.21 91/05/09 17:35:32 Espie * pref_xxx added, * minor modifications in gadget handling. * hook for appwindow: returns the window now, * events.c takes care of the signal mask. * * Revision 1.20 91/05/08 15:50:50 Espie * Volume slider now working. * Warning ! Nothing to add another slider right now. * * Revision 1.19 91/05/07 12:12:22 Espie * *** empty log message *** * * Revision 1.18 91/05/07 02:53:53 Espie * Added smart ``resize window''. * Will now move the window if anything is amiss. * Experimented with the GetScreenDrawInfo aspect ratio. * * Revision 1.17 91/05/06 23:35:47 Espie * Completely cleaned up all codes, * added new entries, like temporary_title/restore_title, * suppressed obsolete ones. Still needs to get gadgets/borders... * tight. * * Revision 1.16 91/05/05 04:01:26 Espie * Added the void_title() call. * Changed minor details. * * Revision 1.15 91/05/03 02:49:14 Espie * Added sizing gadget. Don't like it, so commented it out * (Intuition spends its time trashing its own borders... not neat. * Also changed order of gadgets. Load is most important gadget, * so it is first. Restart and pause are arranged for graphical * symbols (sigh...) |< and '' . * * Revision 1.14 91/05/03 00:42:15 Espie * Cosmetic changes * * Revision 1.13 91/05/02 23:29:50 Espie * Cosmetic changes. Starting message when no song is loaded yet. * * Revision 1.12 91/05/02 11:20:13 Espie * Handle a variable number of gadget much better. * Added banner, start_player() conditionnally if there IS a song to play. * * Revision 1.11 91/05/02 01:30:28 Espie * Many changes in the handle events part, simpler than before. * * Revision 1.10 91/05/01 00:49:32 Espie * Fonts working, resizing ok. * * Revision 1.9 91/04/30 16:51:03 Espie * Added a real button for speed. * Handle font changes a little better... * * Revision 1.8 91/04/30 01:48:11 Espie * Some minor problems with tempo changing not when it should, fixed. * * Revision 1.7 91/04/30 00:36:17 Espie * Stable version III. * * Revision 1.6 91/04/30 00:24:56 Espie * New reset_tempo() command, for using the current state of the speed * setting when swapping songs. * * Revision 1.5 91/04/29 23:52:41 Espie * Added possibility to load files on the fly. * Surprisingly simple, even if limited right now * (I need the possibility to spawn my subtasks, * that would GREATLY simplify most of the program). * * Revision 1.4 91/04/29 15:04:13 Espie * Small modifications to take advantage of the new interface/audio_soft/audio_hard. * Corrected a misconception concerning toggleselect gadgets: * check their state BEFORE waiting for them, and don't assume * you can keep a copy of their internal state. * Guaranteed way to get out of touch with intuition... * * Revision 1.3 91/04/29 13:03:16 Espie * Interfaced to new audio_soft. * * Revision 1.2 91/04/29 03:52:29 Espie * A start for a real interface. * Displays the song title and patterns, * has boolean gadgets for pause, restart, change speed. * Misses some imagery yet... * * Revision 1.1 91/04/29 02:23:15 Espie * Initial revision * * */ #include #include #include #include #include #include #include #include #include #include #include "proto.h" #include "banner.c" /* see */ #ifdef SIZE_GADGET struct saveclip *saveclip; #endif /* this is all the parameters we might want to change * depending on the current screen type */ /* the colors. */ UBYTE TEXT, SHINE, SHADOW, HILIGHT, BACKGROUND; /* the highlighting method for boolean gadgets */ ULONG GAD_HILIGHT; /* the spacing between graphics elements */ int xspace, yspace; struct Window *window; struct Screen *screen; struct RastPort *rastport; int sys_interwidth, sys_baseline, scr_interwidth, scr_baseline; void setup_environment(void) { /* get all we can */ screen = window->WScreen; rastport= window->RPort; sys_interwidth = rastport->TxHeight + 1; sys_baseline = rastport->TxBaseline; scr_interwidth = screen->RastPort.TxHeight + 1; scr_baseline = screen->RastPort.TxBaseline; /* colors have changed under 2.0, these are the old Pen Numbers, * and should provide reasonable defaults */ SHINE = 1; SHADOW = 2; TEXT = 2; HILIGHT = 1; BACKGROUND = 0; GAD_HILIGHT = GADGHIMAGE; xspace = 4; yspace = 4; if (running_20()) { struct DrawInfo *di; di = GetScreenDrawInfo(window->WScreen); if (di->dri_Version >= 1) { if (di->dri_Flags & DRIF_NEWLOOK) { TEXT = di->dri_Pens[textPen]; HILIGHT = di->dri_Pens[hilighttextPen]; SHADOW = di->dri_Pens[shadowPen]; SHINE = di->dri_Pens[shinePen]; BACKGROUND = di->dri_Pens[backgroundPen]; if (SHADOW == SHINE) GAD_HILIGHT = GADGHCOMP; } xspace = 108/di->dri_Resolution.X; yspace = 108/di->dri_Resolution.Y; } FreeScreenDrawInfo(window->WScreen, di); } else /* non interlace screen */ if (screen->Height < 300) yspace = 2; } #define MAX_VOLUME 16 #define REAL_MAX_VOLUME 257 /*** * * The gadget creation routines. * * The basic function are ``object declarations''. * Since these structures don't change very often, * storage is static right now * ***/ /* modify the following constants if you want to add some stuff. */ #define N_GAD 6 #define N_BORDER 6 #define N_COORD 36 #define N_INTUI 6 #define M_NUMBER 11 #define N_PROP 1 #define N_IMAGE 0 /* the actual gadget numbers */ #define LOAD_NUMBER 0 #define RESTART_NUMBER 1 #define PAUSE_NUMBER 2 #define SPEED_NUMBER 3 #define MODE_NUMBER 4 #define VOLUME_NUMBER 5 LOCAL struct Gadget g_array[N_GAD], *gad; LOCAL struct Border b_array[N_BORDER], *border; LOCAL struct IntuiText lab_array[N_INTUI], *label, *status; LOCAL SHORT XY_array[N_COORD], *XY; LOCAL struct PropInfo p_array[N_PROP], *prop; LOCAL struct Image i_array[N_IMAGE + N_PROP], *image; /* the fixed messages */ #define M_PAL 0 #define M_SPEED 5 #define M_RESTART 5 #define M_PAUSE 6 #define M_LOAD 7 #define M_STD 8 #define MAX_SPEED 5 #define MAX_MODE 3 LOCAL char *that[M_NUMBER] = {"PAL", "NTSC", "Fast", "Scan", "Slow", "Restart", "Pause", "Load", "Std", "Old", "New"}; /* the changing message */ /* 30 chars for the song name, + 8 for the numbers --> 40 is plenty enough. */ #define L_STATUS 0 LOCAL char buffer[40]; /* to compute the extent of the display elements */ LOCAL int xmax, ymax; /* we have to offset everything by the window border */ #define relx(a) (window->BorderLeft + (a)) #define rely(a) (window->BorderTop + (a)) /*** * * the text display functions * ****/ LOCAL void setup_status_line(void) { label->FrontPen = TEXT; label->BackPen = BACKGROUND; /* need JAM 2 for fast updates */ label->DrawMode = JAM2; label->LeftEdge = relx(4 * xspace); label->TopEdge = rely(yspace); label->ITextFont = NULL; label->IText = buffer; label->NextText = NULL; assert(label - lab_array < N_INTUI); status = label++; } /* erase_color()/restore_text(): for cleaning up some text. */ LOCAL int oldcolor; LOCAL void erase_color(struct IntuiText *t) { oldcolor = t->FrontPen; t->FrontPen = 0; } LOCAL void restore_color(struct IntuiText *t) { t->FrontPen = oldcolor; } LOCAL void display_status() { PrintIText(rastport, status, 0, 0); } /* external hook for audio_soft.c: show whether we have the audio currently, * or not. */ void have_audio(BOOL yep) { if (yep) status->FrontPen = HILIGHT; else status->FrontPen = TEXT; display_status(); } /*** * * The gadget creation functions * ***/ /* init_gads: setup everything for gadget creation */ LOCAL void init_gads(void) { int i; for (i = 0; i < N_GAD; i++) { g_array[i].NextGadget = g_array+i+1; g_array[i].GadgetID = i; g_array[i].Flags = 0; } g_array[N_GAD-1].NextGadget = NULL; gad = g_array; XY = XY_array; border = b_array; label = lab_array; prop = p_array; image = i_array; xmax = 0; ymax = 0; } /* border creation functions */ LOCAL void point(SHORT x, SHORT y) { *XY++ = x; *XY = y; assert(XY - XY_array < N_COORD); XY++; } LOCAL struct Border *allocate_border(int npoints, int pen) { border->LeftEdge = 0; border->TopEdge = 0; border->FrontPen = pen; border->XY = XY; border->Count = npoints; border->DrawMode = JAM1; border->NextBorder = NULL; assert(border - b_array < N_BORDER); return border++; } /* setting gadget parameters */ LOCAL void set_pos(int x, int y) { gad->LeftEdge = x; gad->TopEdge = y; } LOCAL void set_size(int width, int height) { int xx, yy; gad->Width = width; gad->Height = height; xx = gad->Width + gad->LeftEdge; yy = gad->Height + gad->TopEdge; if (xx > xmax) xmax = xx; if (yy > ymax) ymax = yy; } LOCAL void set_bool(void) { gad->GadgetType = BOOLGADGET; gad->Flags = GAD_HILIGHT; gad->Activation = GADGIMMEDIATE | RELVERIFY; } LOCAL void set_toggle(void) { gad->GadgetType = BOOLGADGET; gad->Flags = GAD_HILIGHT; gad->Activation = GADGIMMEDIATE | TOGGLESELECT; } FORWARD LOCAL struct Border *create_box(int pen1, int pen2); LOCAL void reduce_container(void) { gad->LeftEdge++; gad->TopEdge++; gad->Width -= 2; gad->Height -= 2; } LOCAL void set_prop(int maximum, int initial) { gad->UserData = create_box(SHADOW, SHINE); reduce_container(); gad->GadgetType = PROPGADGET; gad->SpecialInfo = prop; gad->Flags = GADGHNONE; gad->Activation = RELVERIFY|GADGIMMEDIATE; gad->GadgetRender = image; prop->Flags = AUTOKNOB|FREEVERT|PROPBORDERLESS; prop->VertBody = MAXPOT / (maximum+1); prop->VertPot = (initial * MAXPOT) / maximum; prop->HorizBody = MAXPOT; } LOCAL void refresh_prop(struct Gadget *g) { DrawBorder(rastport, g->UserData, g->LeftEdge-1, g->TopEdge-1); } /* the gadget labels */ LOCAL void init_label(void) { label->FrontPen = TEXT; label->BackPen = BACKGROUND; label->DrawMode = JAM1; label->LeftEdge = xspace; label->TopEdge = yspace; label->ITextFont = window->WScreen->Font; label->NextText = NULL; } LOCAL void setup_label(int n) { init_label(); label->IText = that[n]; gad->GadgetText = label; assert(label - lab_array < N_INTUI); label++; } /* each button can have the same size */ LOCAL int size_labels(void) { int l, needed, i; needed = 0; init_label(); for (i = 0; i < M_NUMBER; i++) { label->IText = that[i]; l = IntuiTextLength(label); if (l > needed) needed = l; } return needed; } /* more intricate gadget creation functions */ /* this is an ``highlight box'' ala 2.0 */ LOCAL struct Border *create_box(int pen1, int pen2) { struct Border *first, *second; first = allocate_border(3, pen1); point(0, gad->Height - 1); point(gad->Width - 1, gad->Height - 1); point(gad->Width - 1, 0); second = allocate_border(3, pen2); second->NextBorder = first; point(0, gad->Height - 1); point(0, 0); point(gad->Width - 1, 0); return second; } LOCAL void set_box(void) { gad->GadgetRender = create_box(SHADOW, SHINE); gad->SelectRender = create_box(SHINE, SHADOW); } /* the boxes are shared between gadgets */ LOCAL void same_box(struct Gadget *other) { gad->GadgetRender = other->GadgetRender; gad->SelectRender = other->SelectRender; } #if 0 /* this stuff might be used to add a graphic to some gadgets... later */ LOCAL void add_render(struct Border *border) { border->NextBorder = gad->GadgetRender; gad->GadgetRender = border; } LOCAL void add_select(struct Border *border) { border->NextBorder = gad->SelectRender; gad->SelectRender = border; } #endif LOCAL void next_gadget(void) { assert(gad - g_array < N_GAD); gad++; } LOCAL void setup_gadgets(BOOL req_avail) { int width, height, x, y; /* I want some room for a message * and for a proportional gadget */ x = relx(4 * xspace); y = rely(2 * yspace + sys_interwidth); height = scr_interwidth + 2 * yspace; width = size_labels() + 2 * xspace; /* these need to get first */ set_size(width, height); set_box(); /* requester gadget */ if (req_avail) { set_pos(x, y); set_size(width, height); set_bool(); setup_label(M_LOAD); next_gadget(); x += width + xspace; set_size(width, height); same_box(g_array); } /* restart gadget */ set_pos(x, y); set_bool(); setup_label(M_RESTART); next_gadget(); /* pause gadget */ x += width + xspace; set_pos(x, y); set_size(width, height); set_toggle(); same_box(g_array); setup_label(M_PAUSE); next_gadget(); /* speed gadget */ x += width + xspace; set_pos(x, y); set_size(width, height); set_bool(); same_box(g_array); setup_label(M_PAL); next_gadget(); /* ``mode'' gadget */ x += width + xspace; set_pos(x, y); set_size(width, height); set_bool(); same_box(g_array); setup_label(M_STD); next_gadget(); /* volume gadget */ x = relx(xspace); y = rely(yspace); set_pos(x, y); set_size(2 * xspace, ymax - y); set_prop(MAX_VOLUME, MAX_VOLUME); next_gadget(); xmax+= xspace; ymax+= yspace; } /* changing the window size and title... * this stuff can also deal with resize gadgets. */ LOCAL int xstart = 80, ystart = 40; LOCAL int xcurrent, ycurrent; LOCAL char *window_title = "Experiment IV"; #ifdef SIZE_GADGET LOCAL void delimit(void) { WindowLimits(window, 0, 0, xcurrent + window->BorderRight, ycurrent + window->BorderBottom); } #endif /* safeSizeWindow deals of lots of special cases. * It tries to enlarge (shrink ?) window by dx, dy, * moving it around if it can't do it any other way, * respecting the screen size as it goes. * You usually pass w->Width and w->Height as current width, * current height. * However, for frequent resizes, it is better to keep track * of the size the window should be, because SizeWindow is actually * asynchronous. */ LOCAL void safeSizeWindow(struct Window *w, int dx, int dy, int cw, int ch) { int nw, nh, ddx, ddy; if (dx > 0) { nw = cw + dx; if (nw > w->WScreen->Width) { nw = w->WScreen->Width; dx = nw - w->Width; } ddx = w->WScreen->Width - w->LeftEdge - nw; if (ddx > 0) ddx = 0; } else ddx = 0; if (dy > 0) { nh = ch + dy; if (nh > w->WScreen->Height) { nh = w->WScreen->Height; dy = nh - w->Height; } ddy = w->WScreen->Height - w->TopEdge - nh; if (ddy > 0) ddy = 0; } else ddy = 0; if (ddx || ddy) MoveWindow(w, ddx, ddy); if (dx || dy) SizeWindow(w, dx, dy); } LOCAL void add_borders(void) { safeSizeWindow(window, window->BorderRight + xmax - xstart, window->BorderBottom + ymax - ystart, xstart, ystart); xcurrent = xmax; ycurrent = ymax; #ifdef SIZE_GADGET delimit(); #endif } LOCAL void resize(int xnew, int ynew) { safeSizeWindow(window, xnew - xcurrent, ynew - ycurrent, xcurrent + window->BorderRight, ycurrent + window->BorderBottom); xcurrent = xnew; ycurrent = ynew; #ifdef SIZE_GADGET delimit(); #endif } LOCAL void setup_title(void) { SetWindowTitles(window, -1, banner); ToClean3(SetWindowTitles, window ,0, 0); } void temporary_title(char *change) { SetWindowTitles(window, change, -1); } void restore_title(void) { SetWindowTitles(window, window_title, -1); } LOCAL void setup_all() { init_gads(); setup_status_line(); setup_gadgets(init_requester(window)); add_borders(); #ifdef SIZE_GADGET saveclip = cliptoborder(window, NULL); #endif setup_title(); AddGList(window, g_array, ~0, gad - g_array, NULL); ToClean3(RemoveGList, window, g_array, gad - g_array); refresh_prop(g_array+VOLUME_NUMBER); start_player(); RefreshGList(g_array, window, NULL, gad - g_array); } /* externally accessible function */ struct Window *init_interface(int xoffset, int yoffset) { struct NewWindow template; template.Type = WBENCHSCREEN; template.TopEdge = yoffset; template.LeftEdge = xoffset; template.DetailPen = -1; template.BlockPen = -1; template.IDCMPFlags = CLOSEWINDOW | REFRESHWINDOW #ifdef SIZE_GADGET | NEWSIZE #endif | GADGETDOWN | GADGETUP; template.Flags = WINDOWDEPTH | WINDOWCLOSE | WINDOWDRAG #ifdef SIZE_GADGET | WINDOWSIZING #endif | SIZEBBOTTOM | SIMPLE_REFRESH; template.FirstGadget = NULL; template.CheckMark = NULL; template.Title = window_title; template.MinWidth = 0; template.MinHeight = 0; template.MaxWidth = ~0; template.MaxHeight = ~0; template.Width = xstart; template.Height = ystart; window = OpenWindow(&template); if (window) ToClean(CloseWindow, window); else mayPanic("Could not open window"); setup_environment(); /* setup the window like it should be */ setup_all(); no_title(); return window; } /*** * * Status line management * ***/ LOCAL void change_title(void) { int needed; needed = TextLength(rastport, buffer, strlen(buffer)) + 5 * xspace; resize(needed > xmax ? needed : xmax, ymax); display_status(); } void new_title(int pos, int len, char *title) { erase_color(status); display_status(); restore_color(status); sprintf(buffer, "%3d:%-3d %s \0", pos, len, title); change_title(); } void update_title(int pos, int len, char *title) { sprintf(buffer, "%3d:%-3d %s \0", pos, len, title); display_status(); } void no_title(void) { erase_color(status); display_status(); restore_color(status); sprintf(buffer, "No song loaded\0"); change_title(); } LOCAL int current_mode = 0; LOCAL void reset_mode() { if (current_mode >= MAX_MODE) current_mode = 0; erase_color(g_array[MODE_NUMBER].GadgetText); RefreshGList(g_array+MODE_NUMBER, window, NULL, 1); g_array[MODE_NUMBER].GadgetText->IText = that[M_STD + current_mode]; restore_color(g_array[MODE_NUMBER].GadgetText); RefreshGList(g_array+MODE_NUMBER, window, NULL, 1); set_mode(current_mode); } LOCAL void toggle_mode() { current_mode++; reset_mode(); } void pref_mode(int mode) { current_mode = mode; reset_mode(); } /* internal (and external) tempo management */ LOCAL int current_speed = 0; LOCAL int tempo[] = {256, 213, 170, 110, 550}; LOCAL int effect[] = {256, 213, 170, 20, 550}; /* in some conditions, we need to put back the tempo to its * natural value (i.e., after a pause) */ void reset_tempo(void) { if (current_speed >= MAX_SPEED) current_speed = 0; erase_color(g_array[SPEED_NUMBER].GadgetText); RefreshGList(g_array+SPEED_NUMBER, window, NULL, 1); g_array[SPEED_NUMBER].GadgetText->IText = that[M_PAL + current_speed]; restore_color(g_array[SPEED_NUMBER].GadgetText); RefreshGList(g_array+SPEED_NUMBER, window, NULL, 1); set_tempo(tempo[current_speed], effect[current_speed]); } void pref_speed(int speed) { current_speed = speed; reset_tempo(); } LOCAL void toggle_tempo(void) { current_speed++; reset_tempo(); } LOCAL void reset_volume(void) { set_volume(REAL_MAX_VOLUME * (MAXPOT - prop->VertPot) /MAXPOT); } void pref_volume(int volume) { NewModifyProp(g_array+VOLUME_NUMBER, window, NULL, prop->Flags, prop->HorizPot, MAXPOT - volume*MAXPOT/100, prop->HorizBody, prop->VertBody, VOLUME_NUMBER); reset_volume(); } /* the proper event handling */ LOCAL BOOL aborted; LOCAL void add_to_idcmp(ULONG val) { ModifyIDCMP(window, window->IDCMPFlags | val); } LOCAL void remove_from_idcmp(ULONG val) { ModifyIDCMP(window, window->IDCMPFlags & ~val); } /* some gadgets only react to gadget up, other to gadget down, * some to both ! */ LOCAL void gadget_up(ULONG id) { switch(id) { case RESTART_NUMBER: launch_play(0); break; case LOAD_NUMBER: try_change_song(); break; case VOLUME_NUMBER: remove_from_idcmp(INTUITICKS); break; default: break; } } LOCAL void gadget_down(ULONG id) { switch(id) { case SPEED_NUMBER: toggle_tempo(); break; case PAUSE_NUMBER: if (g_array[PAUSE_NUMBER].Flags & SELECTED) stop_player(); else start_player(); break; case MODE_NUMBER: toggle_mode(); break; /* the volume gadget turns on ``busy update'' * while selected. If I add more prop gadgets, * I will have to keep track of which ones to * update. */ case VOLUME_NUMBER: add_to_idcmp(INTUITICKS); break; /* right now, the state of the pause gadget is * constantly tested... because ``external events'' * (like the availability of a song) might * change the behaviour to follow. */ default: break; } } LOCAL void process_message(struct IntuiMessage *msg) { static ULONG class; static void *object; static struct Gadget *gadget; class = msg->Class; object = msg->IAddress; ReplyMsg((struct Message *)msg); switch(class) { case CLOSEWINDOW: aborted = TRUE; break; case GADGETDOWN: gadget = (struct Gadget *)object; gadget_down(gadget->GadgetID); break; case GADGETUP: gadget = (struct Gadget *)object; gadget_up(gadget->GadgetID); break; case INTUITICKS: reset_volume(); break; #ifdef SIZE_GADGET case NEWSIZE: xcurrent = window->Width - window->BorderRight; ycurrent = window->Height - window->BorderBottom; recliptoborder(saveclip); break; #endif case REFRESHWINDOW: BeginRefresh(window); display_status(); refresh_prop(g_array+VOLUME_NUMBER); EndRefresh(window, TRUE); break; default: break; } } /* access through this one is needed by the filerequester */ void message_interface(struct IntuiMessage *msg) { process_message(msg); } void handle_interface(void) { struct IntuiMessage *msg; while(msg = (struct IntuiMessage *)GetMsg(window->UserPort)) process_message(msg); } /* concerns events.c exclusively */ BOOL did_abort(void) { return aborted; } void clear_abort(void) { aborted = FALSE; }