1 /* pMARS -- a portable Memory Array Redcode Simulator
2 * Copyright (C) 1993-1995 Albert Ma, Na'ndor Sieben, Stefan Strack, Mintardjo Wangsawidjaja and Martin Maierhofer
3 * Copyright (C) 2003 M Joonas Pihlaja
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19 #include <string.h>
20 #include <ctype.h>
21 #include <assert.h>
22 #include "SDL.h"
23
24 #define USE_BLIT_FONTS 1
25
26
27 /* ------------------------------------------------------------------------
28 * Types and globals.
29 */
30
31 /*-- Externs --*/
32 extern char *CDB_PROMPT;
33 extern int curPanel; /* # current cdb panel: 1 or 2. */
34 extern int curAddr; /* current core address in cdb. */
35
36 extern void sighandler(int dummy); /* Used to tell cdb that the user
37 has interrupted a fight. */
38 /* Strings. */
39 extern char *outOfMemory;
40 extern char *errDisplayOpen;
41 extern char *failedSDLInit;
42 extern char *badModeString;
43 extern char *pressAnyKey;
44 extern char *errSpriteConv;
45 extern char *errSpriteColKey;
46 extern char *errSpriteSurf;
47 extern char *errSpriteFill;
48 extern char *errorHeader;
49
50
51 /*-- Colours --*/
52 typedef struct RGBColour_st {
53 Uint8 r,g,b;
54 } RGBColour;
55
56 #define NCOLOURS 16
57 #define L 205 /* low intensity */
58 #define G 211 /* light grey intensity */
59 #define D 190 /* grey intensity */
60 #define H 255 /* high intensity */
61 static const RGBColour PaletteRGB[NCOLOURS] = {
62 #define BLACK 0
63 { 0, 0, 0 }, /* black */
64 { 0, 0, L }, /* blue3 */
65 { 0, L, 0 }, /* green3 */
66 { 0, L, L }, /* cyan3 */
67 #define RED 4
68 { L, 0, 0 }, /* red3 */
69 { L, 0, L }, /* magenta3 */
70 { L, L, 0 }, /* yellow3 */
71 #define LIGHTGREY 7
72 { G, G, G }, /* light grey */
73 #define GREY 8
74 { D, D, D }, /* grey */
75 { 0, 0, H }, /* blue1 */
76 #define GREEN 10
77 { 0, H, 0 }, /* green1 */
78 { 0, H, H }, /* cyan1 */
79 #define LIGHTRED 12
80 { H, 0, 0 }, /* red1 */
81 { H, 0, H }, /* magenta1 */
82 #define YELLOW 14
83 { H, H, 0 }, /* yellow1 */
84 #define WHITE 15
85 { H, H, H } /* white */
86 };
87 #undef L
88 #undef G
89 #undef D
90 #undef H
91
92 static Uint32 Colours[NCOLOURS]; /* Display format colours */
93 /* that need to be converted. */
94
95 /*-- The display --*/
96
97 SDL_Surface *TheSurf; /* The display surface. */
98
99 typedef struct VMode_st { /* Video mode info */
100 Uint16 w; /* width */
101 Uint16 h; /* height */
102 Uint16 bpp; /* bits per pixel */
103 Uint32 flags; /* flags used to open the surface. */
104 } VMode;
105
106 VMode TheVMode; /* Current video mode. */
107
108 #define NUM_VMODE_FLAGS 5
109 #define DEFAULT_VMODE_FLAGS (SDL_RESIZABLE | SDL_SWSURFACE);
110 #define DEFAULT_VMODE_W 640
111 #define DEFAULT_VMODE_H 480
112 #define DEFAULT_VMODE_BPP 0
113 static struct {
114 const char *name; /* full name of option */
115 size_t siglen; /* # significant chars */
116 Uint32 value; /* bitmask of flags to set or clear. */
117 } const VMode_flags[NUM_VMODE_FLAGS] = {
118 { "fullscreen", 1, SDL_FULLSCREEN },
119 { "resizable", 1, SDL_RESIZABLE },
120 { "any", 1, SDL_ANYFORMAT },
121 { "noframe", 1, SDL_NOFRAME },
122 { "db", 1, SDL_DOUBLEBUF }
123 };
124
125
126 /*-- Font --*/
127
128 /* Font data is held in a NUMCHARS*HEIGHT byte bitmap, where the
129 * bitmap for the Ith character consists of HEIGHT consecutive bytes
130 * at index I*HEIGHT. The HEIGHT bytes form a 8 by HEIGHT sized
131 * bitmap, where the top left bit of the bitmap is the most
132 * significant bit of the first byte. For example, the bitmap
133 * for the character `L' in an 8 by 8 font might be:
134 *
135 * byte #\bit # 7 6 5 4 3 2 1 0
136 * 0 0 0 0 0 0 0 0 0
137 * 1 0 1 0 0 0 0 0 0
138 * 2 0 1 0 0 0 0 0 0
139 * 3 0 1 0 0 0 0 0 0
140 * 4 0 1 0 0 0 0 0 0
141 * 5 0 1 0 0 0 0 0 0
142 * 6 0 1 1 1 1 1 1 0
143 * 7 0 0 0 0 0 0 0 0
144 *
145 * { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7e, 0x00 }
146 * */
147 typedef struct FixedFontInfo_st {
148 const Uint8 *bitmaps; /* glyph bitmaps */
149 Uint16 h; /* height */
150 Uint16 w; /* width, always = 8. */
151 Uint16 nchars; /* # chars */
152 SDL_Surface *glyphs[NCOLOURS]; /* glyphs bitmaps in native format. */
153 } FixedFontInfo;
154
155 /* Compile in a default font to use so the user doesn't have to lug around
156 * a font file. TODO: Let the user choose their own font somehow?
157 **/
158 #include "fnt16.c" /* Font bitmaps */
159 static FixedFontInfo CurrentFont;
160
161
162 /*-- Generic borders --*/
163
164 typedef struct Border_st {
165 Sint16 lft, rgt, top, bot;
166 /* Width's of borders on the left, right, top and bottom edges. If
167 negative, no visible border is drawn on the respective edge(s). */
168 int colix; /* Border colour index. */
169 } Border;
170
171 static const Border noborder = { 0, 0, 0, 0, BLACK };
172
173
174 /*-- Panels --*/
175 typedef struct Panel_st {
176 SDL_Rect r; /* Rectangle of this panel in
177 display. Includes border,
178 if the panel has one. */
179 SDL_Rect v; /* The visual rectangle (R sans B).*/
180 Border b; /* The border. */
181 } Panel;
182
183
184 /*-- Generic Panel Layouts --*/
185
186 typedef int (*layout_func)(Panel *p, void *parms);
187
188 typedef struct Layout_st {
189 Panel p; /* Panel for this layout. */
190 Uint16 minw, maxw; /* Bounds for width and height. */
191 Uint16 minh, maxh;
192
193 struct Layout_st *parent; /* Pair layout that contains */
194 /* this layout, or NULL for a */
195 /* root layout. */
196
197 /* The next fields specify call-backs for the user's layouts. In
198 * the tree of layouts, all callbacks are called in postorder
199 * unless otherwise noted. */
200 void *parms; /* Parameters passed to the
201 all callbacks. */
202
203 int (*before_layout)(struct Layout_st *, Uint16, Uint16);
204 /* Called before proposing a layout with the new width and
205 height of the display so that panels may adjust their
206 minimum/maximum sizes. Returns 1: ok to layout, 0: layout
207 will fail (display too small). */
208
209 layout_func after_layout;
210 /* Called after the sizes and positions of layed out panels
211 have been decided, to give a chance for the user to fail a
212 given layout and do other processing. Returns 1: layout ok,
213 0: layout failed. */
214
215 layout_func doclear;
216 /* Called to clear the layout. Return value ignored. Called in
217 preorder. */
218
219 layout_func doredraw;
220 /* Called when a panel needs to be redrawn. Return value ignored.
221 Called in preorder. */
222
223 void (*doremode)(void *parms);
224 /* Called after a successfully laying out all layouts and the
225 display mode has (possibly) changed. */
226
227 void (*doclose)(void *parms);
228 /* Called to close a layout. */
229
230 layout_func dorefresh;
231 /* Called to refresh a layout. Return value ignored. */
232
233 /* The following fields are used only for pair layouts to control
234 * layout splits. */
235 struct Layout_st *a, *b; /* children. */
236 Uint32 size; /* percentage of split for child b. */
237 Uint16 method; /* Method of split. */
238 } Layout;
239
240 /* When a layout is split we must specify how the new layout is placed in
241 relation to the old layout. */
242 #define BELOWS 0
243 #define ABOVES 1
244 #define LEFTS 2
245 #define RIGHTS 3
246
247 #define is_horizontal_split(method) (((method) & 2) == 2)
248
249 #define UNBOUNDED 32767 /* Maximum value of any minimum or
250 maximum bound for the height or
251 width of a layout. */
252
253 /* The memory for layout structures is statically allocated, and we provide a
254 simple layout allocator from an array of available layouts. When in the
255 free-list, layouts are linked together by they `a' field of the Layout
256 structure. */
257 #define MAXLAYOUTS 40
258 Layout *layout_free_list = NULL;
259 Layout all_layouts[MAXLAYOUTS];
260
261 /*-- Generic Text Output panels (used for all text panels.) --*/
262
263 typedef struct TextOutput_st {
264 Layout *layout;
265 Sint16 x, y; /* Point coordinates. */
266 SDL_Rect refresh; /* Rectangle that needs refreshing. */
267 SDL_Rect drawn; /* Rectangle where stuff was drawn. */
268 struct TextOutput_st *next; /* Next free TextOutput in free list.*/
269 } TextOutput;
270
271
272 /* pMARS specific: There must be two more TextOutputs than text inputs
273 * because both the status line and warrior names panels are TextOutputs. */
274 #define MAXTEXTOUTPUTS 22
275 static TextOutput all_textoutputs[MAXTEXTOUTPUTS];
276 static TextOutput *textoutput_free_list = NULL;
277
278
279 /*-- Generic Text Input panels (used for cdb text panels.) --*/
280
281 typedef struct TextInput_st {
282 TextOutput *out; /* The output panel. */
283 Layout *layout; /* layout of this text input. */
284 int active; /* set if accepting input. */
285 int has_focus; /* set if has focus. */
286 int needs_prompting; /* set if prompts on activation.*/
287 char prompt[MAXALLCHAR]; /* prompt string. */
288 char buf[MAXALLCHAR]; /* input buffer. */
289 size_t len; /* input length. */
290 int colix; /* colour of user input. */
291
292 struct TextInput_st *next; /* Next free TextInput in free-list. */
293 } TextInput;
294
295 /* TextInput structures are also statically allocated and provide a simple
296 * free-list allocator. */
297 #define MAXTEXTINPUTS 10
298 TextInput all_textinputs[MAXTEXTINPUTS];
299 TextInput *textinput_free_list = NULL;
300
301
302 /*-- (pMARS specific) --*/
303
304 static Uint32 WarColourIx[MAXWARRIOR]; /* Colour index (into Colours[]) of */
305 static Uint32 DieColourIx[MAXWARRIOR]; /* warriors and the colours they die
306 with.. */
307
308 /* What core cells look like. */
309 typedef struct CellInfo_st {
310 int box_size; /* size of one "pixel" of a cell. */
311 int interbox_space; /* # pixels between cell's boxes. */
312 int intercell_space; /* # pixels between cells. */
313 } CellInfo;
314
315 /* The different cell sizes chosen by the pMARS display mode. */
316 static const CellInfo DesiredCells[10] = {
317 { 1, 0, 2 }, /* 0 */
318 { 1, 0, 2 }, /* 1 */
319 { 1, 0, 3 }, /* 2 */
320 { 2, 0, 2 }, /* 3 */
321 { 3, 0, 1 }, /* 4 */
322 { 3, 1, 1 }, /* 5 */
323 { 4, 0, 1 }, /* 6 */
324 { 5, 2, 3 }, /* 7 */
325 { 10, 5, 8 }, /* 8 */
326 { 20, 10, 16 }, /* 9 */
327 };
328
329 /* The arena (core). */
330 typedef struct Arena_st {
331 Layout *layout; /* Layout used for a core. */
332 unsigned char *core; /* For each core cell, four colour
333 indices that say what the colour of
334 the cell boxes are in the top-left,
335 top-right, bottom-right, and
336 bottom-left */
337 Uint32 coresize; /* Guess what? */
338 CellInfo cell; /* The actual cell used that fits
339 the core display. */
340 CellInfo desired_cell; /* The cell chosen by the current
341 pMARS display mode. */
342
343 SDL_Surface ***blitboxes;
344 /* For each used colour index (into Colours[]) we precompute sixteen
345 sprites, each representing a different way that the top-left,
346 top-right, bottom-left and bottom-right boxes of a 2x2 grid can be
347 filled with that colour. During a fight these are then blitted to
348 the screen using a potentially fast blit. (For very small core
349 cells this is a lot of overhead, but it should pay off for larger
350 cells.) This precomputation is done each time the doremode()
351 function of a core layout is called. */
352 } Arena;
353
354 /* Bit masks of the top-left, top-right, bottom-left, and bottom-right boxes
355 of a cell. */
356 #define TL 0x00000001
357 #define TR 0x00000002
358 #define BL 0x00000004
359 #define BR 0x00000008
360
361 static Layout *CoreLayout; /* The Layout used for the core
362 display. */
363 static Arena CoreArena; /* The Arena used for the core
364 display. */
365 int ArenaX, ArenaY; /* The top-left coordinates of the
366 cell at address 0 in core. */
367 int CellSize; /* # pixels used by a core cell. */
368 int CellsPerLine; /* # cells that fit in one row. */
369 SDL_Surface ***BlitBoxes; /* Core cell sprites (same array as in
370 CoreArena.blitboxes.) */
371
372 /* Macros to convert pixel offsets from (ArenaX,ArenaY) into core addresses
373 and vice versa. */
374 #define xkoord(addr) (ArenaX + CellSize*((addr) % CellsPerLine))
375 #define ykoord(addr) (ArenaY + CellSize*((addr) / CellsPerLine))
376 #define addr_of(x,y) ( ((x)-ArenaX)/CellSize \
377 + CellsPerLine*((y)-ArenaY)/CellSize )
378
379
380 static Layout *StatusLayout = NULL; /* The Layout used for the status
381 line. */
382
383 static Layout *MetersLayout = NULL; /* The Layout used for the process and
384 cycle meters. */
385
386 int MetersX; /* X-coordinate of meters low point. */
387 int splY[MAXWARRIOR]; /* Y-coordinates of process meters. */
388 int cycleY; /* Y-coordinate of cycle counter. */
389 int processRatio; /* Ratio of processes to pixels. */
390 int cycleRatio; /* Ratio of cycles to pixels. */
391 /* Both ratios are at least 1. */
392
393 static TextInput *TextPanels[MAXTEXTINPUTS]; /* cdb input panels, indexed by
394 curPanel-1. */
395 static int NTextPanels = 0;
396
397 /* These are filled in clparse.c */
398 #define SDLGR_NOPTIONS 1
399 int MaxSDLOptions;
400 const char sdlgr_Options[SDLGR_NOPTIONS] = {
401 'm' /* The new -m option is used to give
402 * the desired screen mode. */
403 };
404 const char *sdlgr_Storage[SDLGR_NOPTIONS] = {
405 NULL /* Pointer to argument of -m option */
406 };
407
408 /* Command History. */
409
410 typedef struct Cmd_st {
411 char *cmd; /* The command. */
412 struct Cmd_st *next; /* earlier command in history. */
413 struct Cmd_st *prev; /* later command in history. */
414 } Cmd;
415
416 Cmd CmdRing; /* List header. */
417
418 /* ------------------------------------------------------------------------
419 * File-local prototypes.
420 */
421
422 /* Utilities */
423 static void exit_panic (const char *msg, const char *reason);
424 static void xstrncpy (char *dst, const char *src, size_t maxbuf);
425 static void xstrncat (char *dst, const char *src, size_t maxbuf);
426 static void xstrnapp (char *dst, unsigned int c, size_t maxbuf);
427 static char *xstrdup (const char *s);
428
429 static void putpixel (SDL_Surface *s, int x, int y, Uint32 col);
430
431 static void clip_rect (const SDL_Rect *c, SDL_Rect *r);
432 static void join_rect (const SDL_Rect *r, SDL_Rect *c);
433 static void clear_rect (const SDL_Rect *r);
434
435 /* Display */
436 static const char* decode_vmode_string (VMode *m, const char *str);
437 static void Slock (SDL_Surface *screen);
438 static void Sunlock (SDL_Surface *screen);
439 static SDL_Surface * set_VMode (VMode* m);
440 /* static void clear_display (void); */
441 static void refresh_rect(const SDL_Rect *r);
442
443 /* Text output primitives. */
444 static void outblankxy (Sint16 x, Sint16 y, int colix, const SDL_Rect *clip);
445 static void outcharxy (unsigned char c, Sint16 x, Sint16 y, int colix,
446 const SDL_Rect *clip);
447 static void Font_Init(FixedFontInfo *f, const Uint8* bitmaps,
448 Uint16 w, Uint16 h, Uint16 nchars);
449
450 /* Borders. */
451 static Border border (Sint16 l, Sint16 r, Sint16 t, Sint16 b, int colix);
452 static void draw_border (const SDL_Rect *r, const Border *b);
453
454 /* Panels */
455 static Panel new_panel (Sint16 x, Sint16 y, Uint16 w, Uint16 h, Border b);
456 static void recompute_panel_view (Panel *p);
457 static const SDL_Rect *panel_view (const Panel *p);
458 static void refresh_panel (const Panel *p);
459 static void panel_text_size (const Panel *p, int *cols, int *lines);
460 static void clear_panel (Panel *p);
461
462 /* Layouts; general. */
463 static void init_all_layouts (void);
464 static int default_before_layoutfunc (Layout *a, Uint16 w, Uint16 h);
465 static int default_after_layoutfunc (Panel *p, void *parms);
466 static int default_redrawfunc (Panel *p, void *parms);
467 static int default_clearfunc (Panel *p, void *parms);
468 static int default_refreshfunc (Panel *p, void *parms);
469 static void default_closefunc (void *parms);
470 static void default_remodefunc (void *parms);
471
472 static Layout * alloc_layout (void);
473 static Layout * alloc_pair_layout (void);
474 static void free_layout (Layout *lay);
475 static Uint16 add_bounds (Uint32 a, Uint32 b);
476 /* static Uint16 sub_bounds (Uint32 a, Uint32 b); */
477 static void synthesize_bounds (Layout *pair);
478 static Layout * get_root_layout (Layout *a);
479 static void recursive_free_layout (Layout *a);
480 static Layout * get_sibling_layout (const Layout *a);
481 static void close_layout (Layout *a);
482 static void propose_layout (Layout *pair, Sint16 x, Sint16 y, Uint16 w, Uint16 h);
483 static void redraw_layout_borders (Layout *a);
484 static void clear_layout (Layout *a);
485 static int callback_before_layouts (Layout *a, Uint16 w, Uint16 h);
486 static int callback_after_layouts (Layout *a);
487 static void redraw_layout (Layout *a);
488 static void refresh_layout (Layout *a);
489 static int layout (Layout *root, Sint16 x, Sint16 y, Uint16 w, Uint16 h);
490
491 /* TextOutput layouts. */
492 static void free_text_output(TextOutput *t);
493 static void init_all_text_outputs (void);
494 static int doclear_textoutput (Panel *p, void *parms);
495 static void doclose_textoutput (void *parms);
496 static int dorefresh_textoutput (Panel *p, void *parms);
497 static TextOutput *alloc_text_output (Layout *a);
498 static void clear_to_eol (TextOutput *p);
499 static void cr (TextOutput *t);
500 static void emit (TextOutput *t, unsigned char c, int colix);
501 static void backspace (TextOutput *t);
502 static void backchar (TextOutput *t);
503 static void delchar (TextOutput *t);
504 static void type (TextOutput *t, const char *str, int colix);
505 static void type_nowrap (TextOutput *t, const char *str, int colix);
506
507 /* TextInput layouts. */
508 static void free_text_input (TextInput *t);
509 static void init_all_text_inputs (void);
510 static void cursoron (TextInput *t);
511 static void cursoroff (TextInput *t);
512 static int after_layout_textinput (Panel *p, void *parms);
513 static int doredraw_textinput (Panel *p, void *parms);
514 static void doclose_textinput (void *parms);
515 static int dorefresh_textinput (Panel *p, void *parms);
516 static TextInput * alloc_text_input (Layout *a);
517 static void activate (TextInput *t);
518 static void deactivate (TextInput *t);
519 static int is_active (TextInput *t);
520 static void give_focus (TextInput *t);
521 static void take_focus (TextInput *t);
522 static void set_prompt (TextInput *t, const char *prompt);
523 static void add_char (TextInput *t, char c);
524 static void del_char (TextInput *t);
525 static void reset_input (TextInput *t, const char *str);
526 static void finish_input (TextInput *t, char *buf, size_t maxbuf);
527 static void hide_input (TextInput *t);
528
529 /* Core Arena layout. */
530 static Uint32 get_cell_size (const CellInfo *c);
531 static CellInfo choose_cell (int mode);
532 static void display_box (int addr, Uint32 boxes, int colix);
533 static int decr_cell_size (CellInfo *c);
534 static int before_layout_arena (Layout *layout, Uint16 w, Uint16 h);
535 static int after_layout_arena (Panel *p, void *parms);
536 static void doclose_arena (void *parms);
537 static int doredraw_arena (Panel *p, void *parms);
538 static int doclear_arena (Panel *p, void *parms);
539 static void make_blitboxes_for_colix (Arena *a, int colix);
540 static void make_blitbox (Arena *a, int colix, Uint32 boxes);
541 static void fill_box (SDL_Surface *s, Sint16 x, Sint16 y, Uint16 w, Uint16 h, Uint32 col);
542 static void free_blitboxes(Arena *a);
543 static void doremode_arena (void *parms);
544 static void init_arena (Layout *layout, size_t coresize, int displaymode);
545
546 /* Status line, warrior names, process/cycles meters layouts. */
547 static int doredraw_status (Panel *p, void *parms);
548 static int doredraw_names (Panel *p, void *parms);
549 static int doredraw_meters (Panel *p, void *parms);
550 static int after_layout_meters (Panel *p, void *parms);
551
552 /* Pulling it all together. */
553 static void redraw (void);
554 static void relayout (Uint16 w, Uint16 h);
555 static void init_layout (void);
556
557 /* Cdb text panels. */
558 static int split_text_panel (int panel, int size, Uint16 method);
559 static void close_text_panel (int panel);
560
561 /* Events and keyboard input. */
562 static void default_handler (SDL_Event *e);
563 static char conv_key (const SDL_keysym *s);
564 static int macro_key (const SDL_keysym *s, char *buf, int maxbuf);
565
566 /* Command History */
567 static Cmd * get_cmd_ring();
568 static void add_cmd (const char *cmd);
569
570 /* Events & keyboard. */
571 static void default_handler (SDL_Event *e);
572 static int special_keyhandler (const SDL_keysym *s);
573 static char conv_key (const SDL_keysym *s);
574 static int macro_key (const SDL_keysym *s, char *buf, int maxbuf);
575
576 /* Misc. */
577 static void exit_hook (void);
578
579
580 /* ------------------------------------------------------------------------
581 * Utilities
582 */
583
584 #undef max
585 #undef min
586 #define max(a,b) ((a) < (b) ? (b) : (a))
587 #define min(a,b) ((a) < (b) ? (a) : (b))
588
589 /* Exit in a hurry. */
590 static void
exit_panic(const char * msg,const char * reason)591 exit_panic(const char *msg, const char *reason)
592 {
593 fprintf(stderr, errorHeader, msg);
594 if (reason) {
595 fprintf(stderr, ": %s", reason);
596 }
597 fprintf(stderr, ".\n");
598 Exit(1);
599 }
600
601 /* Safer strncpy() that always nil-terminates (except when maxbuf==0). */
602 static void
xstrncpy(char * dst,const char * src,size_t maxbuf)603 xstrncpy(char *dst, const char *src, size_t maxbuf)
604 {
605 size_t k;
606 if (maxbuf==0) { return; }
607 for (k=0; k<maxbuf-1 && src[k]; k++) {
608 dst[k] = src[k];
609 }
610 dst[k] = 0;
611 }
612
613 /* Saner strncat(). */
614 static void
xstrncat(char * dst,const char * src,size_t maxbuf)615 xstrncat(char *dst, const char *src, size_t maxbuf)
616 {
617 size_t len, k;
618 if (maxbuf==0) { return; }
619 len = strlen(dst);
620 for (k=0; k+len+1<maxbuf && src[k]; k++) {
621 dst[k+len] = src[k];
622 }
623 dst[k+len] = 0;
624 }
625
626 /* Append a character. */
627 static void
xstrnapp(char * dst,unsigned int c,size_t maxbuf)628 xstrnapp(char *dst, unsigned int c, size_t maxbuf)
629 {
630 size_t len;
631 if (maxbuf==0) { return; }
632 len = strlen(dst);
633 if (len+1 < maxbuf) {
634 dst[len] = c;
635 }
636 dst[len+1] = 0;
637 }
638
639 /* Copy a string. */
640 static char *
xstrdup(const char * s)641 xstrdup(const char *s)
642 {
643 char *t;
644 assert(s);
645 t = MALLOC((strlen(s)+1)*sizeof(char));
646 if (t==NULL) {
647 exit_panic(outOfMemory, NULL);
648 }
649 { char *q = t; while ((*q++ = *s++)) {} }
650 return t;
651 }
652
653 /* Compute the clip rectangle R \cap C */
654 static void
clip_rect(const SDL_Rect * c,SDL_Rect * r)655 clip_rect(const SDL_Rect *c, SDL_Rect *r)
656 {
657 Sint16 x1,y1;
658 Sint16 x0,y0;
659 x0 = max(r->x, c->x);
660 y0 = max(r->y, c->y);
661 x1 = min(r->x+r->w, c->x+c->w);
662 y1 = min(r->y+r->h, c->y+c->h);
663 r->x = x0;
664 r->y = y0;
665 r->w = max(x1-x0,0);
666 r->h = max(y1-y0,0);
667 }
668
669 /* C := smallest rectangle containing both R and C. */
670 static void
join_rect(const SDL_Rect * r,SDL_Rect * c)671 join_rect(const SDL_Rect *r, SDL_Rect *c)
672 {
673 if (c->w && c->h) {
674 Sint16 x0,y0;
675 Sint16 x1,y1;
676 x0 = min(r->x, c->x);
677 y0 = min(r->y, c->y);
678 x1 = max(r->x+r->w, c->x+c->w);
679 y1 = max(r->y+r->h, c->y+c->h);
680 c->x = x0;
681 c->y = y0;
682 c->w = x1-x0;
683 c->h = y1-y0;
684 } else {
685 *c = *r;
686 }
687 }
688
689
690 /*
691 * Set the pixel at (x, y) to the given value
692 * NOTE: The surface must be locked before calling this!
693 * This is from the SDL docs, so it can't be all that bad.
694 */
695 static void
putpixel(SDL_Surface * surface,int x,int y,Uint32 pixel)696 putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
697 {
698 int bpp = surface->format->BytesPerPixel;
699 /* Here p is the address to the pixel we want to set */
700 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
701
702 switch(bpp) {
703 case 1:
704 *p = pixel;
705 break;
706
707 case 2:
708 *(Uint16 *)p = pixel;
709 break;
710
711 case 3:
712 if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
713 p[0] = (pixel >> 16) & 0xff;
714 p[1] = (pixel >> 8) & 0xff;
715 p[2] = pixel & 0xff;
716 } else {
717 p[0] = pixel & 0xff;
718 p[1] = (pixel >> 8) & 0xff;
719 p[2] = (pixel >> 16) & 0xff;
720 }
721 break;
722
723 case 4:
724 *(Uint32 *)p = pixel;
725 break;
726 }
727 }
728
729 /* ------------------------------------------------------------------------
730 * Display surface & colour management
731 *
732 * Globals:
733 * SDL_Surface *TheSurf; (read/write)
734 * Display surface.
735 *
736 * VMode TheVMode;
737 * Global video mode.
738 *
739 * NCOLOURS (= 16) (read-only)
740 * The number of colours in _our_ palette. pMARS uses only the 16
741 * standard VGA colours for a uniform look-and-feel. :)
742 *
743 * static const RGBColour PaletteRGB[NCOLOURS]; (read-only)
744 * RGBValues of _our_ palette. Indexed by <colourname> #defines:
745 *
746 * Uint32 Colours[NCOLOURS]; (read-only)
747 * The colours in PaletteRGB[] in the format of the display surface.
748 * These must be initialised using SDL_MapRGB() before use.obs
749 *
750 * Functions:
751 * const char *decode_vmode_string(VMode *mode, const char* argstr);
752 * Decodes a videomode string ARGSTR and places the results in
753 * the video mode info structure MODE. Returns NULL on success.
754 * On failure, returns a pointer to the trailing substring that
755 * wasn't understood.
756 *
757 * SDL_Surface *set_VMode(VMode* mode)
758 * Attempt to set the display video mode to the given MODE and return
759 * a pointer to the opened surface --- NULL on failure. On success
760 * the flags of MODE are set to indicate the actual flags of the surface,
761 * and the colours from RGBColours are mapped into display-native
762 * colours into Colours[].
763 *
764 * void Slock(SDL_Surface *);
765 * void Sunlock(SDL_Surface *);
766 * Lock/Unlock the given surface if necessary.
767 */
768
769
770 /* Decode VMode options from a VMode string. On success, the given
771 * VMode structure contains the desired mode, and the function returns
772 * NULL. Otherwise the function returns a non-NULL pointer to a
773 * trailing substring of the mode string that doesn't make sense.
774 * The option string must have this format:
775 * [<width>x<height>][@<bpp>][:<flag1>[:<flag2>[...[:<flagN>]...]]]
776 * The flags may be preceded by a '-' to indicate that the given
777 * flag must be cleared. Valid examples:
778 *
779 * 640x480 640 by 480 at any bpp, window.
780 * 640x480x16 640 by 480 at 8 bits per pixel window.
781 * 320x200x16:noframe 320 by 200 window without a frame.
782 * :fullscreen default width/height, fullscreen.
783 */
784 static const char*
decode_vmode_string(VMode * m,const char * str)785 decode_vmode_string(VMode *m, const char *str)
786 {
787 int tmp;
788 int set_dims = 0;
789 /* Decode width, height, bpp in the format <WIDTH>x<HEIGHT>[@<BPP>].
790 * bpp==0 means that any pixel format is acceptable (most probably the
791 * current pixel format of the display device).
792 */
793 m->w = DEFAULT_VMODE_W;
794 m->h = DEFAULT_VMODE_H;
795 m->bpp = DEFAULT_VMODE_BPP;
796 m->flags = DEFAULT_VMODE_FLAGS;
797 if (1==sscanf(str, "%d", &tmp)) {
798 if (3!=sscanf(str,"%hux%hu@%hu", &m->w, &m->h, &m->bpp)) {
799 m->w = DEFAULT_VMODE_W;
800 m->h = DEFAULT_VMODE_H;
801 m->bpp = DEFAULT_VMODE_BPP;
802 if (2!=sscanf(str, "%hux%hu", &m->w, &m->h)) {
803 return str;
804 }
805 }
806 set_dims = 1;
807 while (*str && *str!=':') { str++; } /* skip to ':' */
808 } else if (*str=='@') {
809 if (1!=sscanf(str,"@%hu", &m->bpp)) {
810 m->bpp = DEFAULT_VMODE_BPP;
811 return str;
812 }
813 while (*str && *str!=':') { str++; } /* skip to ':' */
814 }
815 if (*str==':') { str++; } /* skip over ':' */
816
817 /* Decode flags separated by ':' and optionally preceded by '-'. */
818 if (m->bpp == 0) { m->flags |= SDL_ANYFORMAT; }
819
820 while (*str) {
821 int i;
822 int set_them = 1;
823 if (str[0]=='-') { set_them = 0; str++; }
824
825 for (i=0; i<NUM_VMODE_FLAGS; i++) {
826 if (strlen(str)>=VMode_flags[i].siglen) {
827 if (0==strncmp(str, VMode_flags[i].name,
828 VMode_flags[i].siglen))
829 {
830 if (set_them) {
831 m->flags |= VMode_flags[i].value;
832 } else {
833 m->flags &= ~VMode_flags[i].value;
834 }
835 break;
836 }
837 }
838 }
839 if (i==NUM_VMODE_FLAGS) {
840 return str; /* unknown flag */
841 }
842 while (*str && *str!=':') { str++; } /* skip to ':' */
843 if (*str) { str++; } /* skip over ':' */
844 }
845
846 /* If the user didn't set the dimensions and requested fullscreen mode,
847 * we should find the biggest fullscreen mode available and use that.
848 * In case no modes are available, or "all" modes are ok, use the defaults.
849 */
850 if (!set_dims && ((m->flags & SDL_FULLSCREEN)==SDL_FULLSCREEN)) {
851 SDL_Rect **modes;
852 modes = SDL_ListModes(NULL, SDL_FULLSCREEN);
853 if (modes == NULL || modes==(SDL_Rect**)(-1)) {
854 /* Use defaults that are set already. */
855 } else {
856 /* Find the biggest fullscreen mode. */
857 int i, bigix = -1;
858 double bigsize = 0;
859 for (i=0; modes[i]; i++) {
860 double size = (double)modes[i]->w*modes[i]->h;
861 if (size>bigsize) {
862 bigsize = size;
863 bigix = i;
864 }
865 }
866 if (bigix>=0) {
867 m->w = modes[bigix]->w;
868 m->h = modes[bigix]->h;
869 }
870 }
871 }
872
873 return 0; /* all ok. */
874 }
875
876 /* Lock a surface if necessary. */
877 static void
Slock(SDL_Surface * screen)878 Slock(SDL_Surface *screen)
879 {
880 if ( SDL_MUSTLOCK(screen) )
881 {
882 while (SDL_LockSurface(screen)<0) {
883 SDL_Delay(50); /* wait for a while. */
884 }
885 }
886 }
887
888 /* Unlock a surface if necessary. */
889 static void
Sunlock(SDL_Surface * screen)890 Sunlock(SDL_Surface *screen)
891 {
892 if ( SDL_MUSTLOCK(screen) )
893 {
894 SDL_UnlockSurface(screen);
895 }
896 }
897
898 /* Attempt to initialise the display to a given video mode M. Returns
899 * the surface structure of the display if successful and modifies the
900 * VMode structure M to reflect the actual flags of the surface. On
901 * failure returns NULL. On success, remaps the colours in Colours[] to
902 * the display native format as a side-effect.
903 **/
904 static SDL_Surface *
set_VMode(VMode * m)905 set_VMode(VMode* m)
906 {
907 SDL_Surface *surf = NULL;
908 Uint32 flags = m->flags;
909
910 if ((flags & SDL_FULLSCREEN) == SDL_FULLSCREEN) { /* only in fullscreen. */
911 const SDL_VideoInfo *info = SDL_GetVideoInfo();
912 if ( info->blit_fill ) {
913 /* We want accelerated blitting. */
914 flags |= SDL_HWSURFACE;
915 }
916 if ((flags & SDL_HWSURFACE) == SDL_HWSURFACE) {
917 /* Direct hardware blitting without double-buffering
918 * causes really bad flickering.
919 */
920 if ( m->bpp>0
921 && info->video_mem*1024 > (Uint32)(m->h*m->w*m->bpp/8) )
922 {
923 flags |= SDL_DOUBLEBUF;
924 } else {
925 flags &= ~SDL_HWSURFACE;
926 }
927 }
928 if ((flags & SDL_HWSURFACE) == SDL_HWSURFACE) {
929 flags &=~ SDL_ANYFORMAT;
930 }
931 }
932
933 if (!(surf = SDL_SetVideoMode(m->w, m->h, m->bpp, flags))
934 && flags != m->flags)
935 {
936 flags = m->flags; /* try with original flags. */
937 surf = SDL_SetVideoMode(m->w, m->h, m->bpp, flags);
938 }
939 m->flags = flags;
940
941 /* Update VMode with the one we actually got. */
942 TheSurf = surf;
943 if (surf) {
944 m->flags = surf->flags;
945 m->w = surf->w;
946 m->h = surf->h;
947 m->bpp = surf->format->BitsPerPixel;
948
949 /* Set the caption on our window.
950 */
951 SDL_WM_SetCaption("pMARS", "pMARS");
952 #if 0
953 printf("%dx%d@%d:%x\n", m->w, m->h, m->bpp, m->flags);
954 #endif
955
956 /* Map the colours we will use into display-native format. */
957 {
958 int k;
959 for (k=0; k<NCOLOURS; k++) {
960 RGBColour c = PaletteRGB[k];
961 Colours[k] = SDL_MapRGB(surf->format, c.r, c.g, c.b);
962 }
963 }
964
965 /* Initialise the font for the colours we will use. */
966 Font_Init(&CurrentFont, fnt16_raw, 8, 16, 128);
967 }
968 return surf;
969 }
970
971
972 #if 0
973 static void
974 clear_display(void)
975 {
976 SDL_Rect r;
977 r.x = 0;
978 r.y = 0;
979 r.w = TheSurf->w;
980 r.h = TheSurf->h;
981 Slock(TheSurf);
982 SDL_FillRect(TheSurf, &r, Colours[BLACK]);
983 Sunlock(TheSurf);
984 }
985 #endif
986
987
988 static void
refresh_rect(const SDL_Rect * r)989 refresh_rect(const SDL_Rect *r)
990 {
991 SDL_UpdateRect(TheSurf, r->x, r->y, r->w, r->h);
992 }
993
994 /* ------------------------------------------------------------------------
995 * Text and font routines.
996 *
997 * Globals:
998 * struct FixedFontInfo_st CurrentFont; (read-only)
999 *
1000 * Functions:
1001 * void outcharxy(unsigned char c, Sint16 x, Sint16 y, Uint32 col,
1002 * const SDL_Rect *clip);
1003 * Draw the glyph for character C in the current font in the
1004 * given (display format) colour COL into the global display
1005 * surface TheSurf. Clip to the rectangle CLIP if it is non-NULL.
1006 *
1007 * void outblankxy(Sint16 x, Sint16 y, Uint32 col, const SDL_Rect *clip);
1008 * Fill an 8 by HEIGHT (of the current font) rectangle whose top-left
1009 * coordinate is (X,Y) with the given colour. Clip to CLIP is non-NULL.
1010 *
1011 * void Font_Init(FixedFontInfo *f, const Uint8 *bitmaps,
1012 * Uint16 w, Uint16 h, Uint16 nchars);
1013 * Initialise the font into display native format.
1014 **/
1015
1016 static void
Font_Init(FixedFontInfo * f,const Uint8 * bitmaps,Uint16 w,Uint16 h,Uint16 nchars)1017 Font_Init(FixedFontInfo *f, const Uint8* bitmaps, Uint16 w, Uint16 h, Uint16 nchars)
1018 {
1019 int i;
1020 Uint32 key;
1021 f->bitmaps = bitmaps;
1022 f->w = w;
1023 f->h = h;
1024 f->nchars = nchars;
1025
1026 /* If bitmaps == NULL, then this is the first call to initialise a
1027 * font and we should load all the display native format surfaces
1028 * with NULL so deallocation works when it is properly initialised. */
1029 if (bitmaps==NULL) {
1030 for (i=0; i<NCOLOURS; i++) {
1031 f->glyphs[i] = NULL; /* clear glyphs for all colours. */
1032 }
1033 return;
1034 }
1035 #if USE_BLIT_FONTS
1036 /* Free the earlier display native format glyphs. */
1037 for (i=0; i<NCOLOURS; i++) {
1038 if (f->glyphs[i]) {
1039 SDL_FreeSurface(f->glyphs[i]);
1040 f->glyphs[i] = NULL;
1041 }
1042 }
1043
1044 /* Create the new glyphs for each colour. */
1045 for (i=0; i<NCOLOURS; i++) {
1046 int c;
1047 Uint32 col;
1048 SDL_Surface *s;
1049 s = SDL_CreateRGBSurface(SDL_ANYFORMAT, w*nchars, h,
1050 TheSurf->format->BitsPerPixel,
1051 TheSurf->format->Rmask,
1052 TheSurf->format->Gmask,
1053 TheSurf->format->Bmask,
1054 0); /* no alpha. */
1055 if (s == NULL) {
1056 exit_panic(errSpriteSurf, SDL_GetError());
1057 }
1058
1059 /* Set colorkey (=black) for this glyphmap. */
1060 key = SDL_MapRGB(s->format, 0, 0, 0); /* colorkey */
1061 if (-1==SDL_SetColorKey(s, SDL_RLEACCEL, key)) {
1062 exit_panic(errSpriteColKey, SDL_GetError());
1063 }
1064
1065 /* Get the foreground colour and clear the background to the
1066 * colorkey. */
1067 col = SDL_MapRGB(s->format, PaletteRGB[i].r,
1068 PaletteRGB[i].g, PaletteRGB[i].b);
1069 fill_box(s, 0, 0, c*w*h, h, key);
1070
1071 /* Draw the individual characters (vewy slowly :/) */
1072 Slock(s);
1073 for (c=0; c<nchars; c++) {
1074 Sint16 x, y;
1075 for (y=0; y<h; y++) {
1076 for (x=0; x<w; x++) {
1077 /* Get a bit from the bitmap. The MSB in a byte
1078 * is the leftmost. */
1079 Uint32 bitno = c*w*h + y*w + x;
1080 Uint32 ix = bitno/8;
1081 Uint8 row = f->bitmaps[ix];
1082 bitno = 7 - (bitno & 7);
1083 if (row & (1<<bitno)) {
1084 putpixel(s, c*w+x, y, col);
1085 }
1086 }
1087 }
1088 }
1089 Sunlock(s);
1090
1091 /* Convert glyphs to video format. */
1092 f->glyphs[i] = SDL_DisplayFormat(s);
1093 SDL_FreeSurface(s);
1094 if (f->glyphs[i] == NULL) {
1095 exit_panic(errSpriteConv, SDL_GetError());
1096 }
1097 }
1098 #endif
1099 }
1100
1101 /* Clear a character sized area. */
1102 static void
outblankxy(Sint16 x,Sint16 y,int colix,const SDL_Rect * clip)1103 outblankxy(Sint16 x, Sint16 y, int colix, const SDL_Rect *clip)
1104 {
1105 SDL_Rect r;
1106 r.x = x; r.y = y;
1107 r.w = CurrentFont.w;
1108 r.h = CurrentFont.h;
1109 if (!clip) {
1110 SDL_Rect c;
1111 c.x = c.y = 0;
1112 c.w = TheSurf->w; c.h = TheSurf->h;
1113 clip_rect(&c, &r);
1114 } else {
1115 clip_rect(clip, &r);
1116 }
1117 Slock(TheSurf);
1118 SDL_FillRect(TheSurf, &r, Colours[colix]);
1119 Sunlock(TheSurf);
1120 }
1121
1122 /* Draw a character at a position. */
1123 static void
outcharxy(unsigned char c,Sint16 x,Sint16 y,int colix,const SDL_Rect * clip)1124 outcharxy(unsigned char c, Sint16 x, Sint16 y, int colix,
1125 const SDL_Rect *clip)
1126 {
1127 SDL_Rect oldclip;
1128 SDL_Rect src, dst;
1129 #if USE_BLIT_FONTS
1130 if (c >= CurrentFont.nchars) { return; }
1131 src.h = dst.h = CurrentFont.h;
1132 src.w = dst.w = CurrentFont.w;
1133 dst.x = x; dst.y = y;
1134 src.x = c*CurrentFont.w;
1135 src.y = 0;
1136 SDL_GetClipRect(TheSurf, &oldclip);
1137 SDL_SetClipRect(TheSurf, clip);
1138
1139 SDL_BlitSurface(CurrentFont.glyphs[colix], &src, TheSurf, &dst);
1140
1141 SDL_SetClipRect(TheSurf, &oldclip);
1142 #else
1143 SDL_Rect r;
1144 int i,j,h;
1145 if (c >= CurrentFont.nchars) { return; }
1146
1147 if (!clip) {
1148 r.x = r.y = 0;
1149 r.w = TheSurf->w;
1150 r.h = TheSurf->h;
1151 } else {
1152 r = *clip;
1153 }
1154 outblankxy(x,y, BLACK, &r);
1155 if (c==' ') { return; }
1156
1157 h = CurrentFont.h;
1158 assert(CurrentFont.w == 8);
1159
1160 /* Draw character pixel by pixel. Replace with something faster
1161 * if it's too slow. */
1162 Slock(TheSurf);
1163 for (j=r.y; j<r.y+r.h; j++) {
1164 Uint8 row = CurrentFont.bitmaps[c*h+j-y];
1165 for (i=r.x; i<r.x+r.w; i++) {
1166 if (row & (1<<(7-(i-x)))) {
1167 putpixel(TheSurf, i, j, Colours[colix]);
1168 }
1169 }
1170 }
1171 Sunlock(TheSurf);
1172 #endif /* USE_BLIT_FONTS */
1173 }
1174
1175
1176 /* ------------------------------------------------------------------------
1177 * Panels
1178 *
1179 * Panels are rectangular areas on the display surface that may have a border.
1180 **/
1181
1182 /* Create a new border. */
1183 static Border
border(Sint16 l,Sint16 r,Sint16 t,Sint16 b,int colix)1184 border(Sint16 l, Sint16 r, Sint16 t, Sint16 b, int colix) {
1185 Border e;
1186 e.lft = l; e.rgt = r; e.top = t; e.bot = b; e.colix = colix;
1187 return e;
1188 }
1189
1190 /* Draw a border. */
1191 static void
draw_border(const SDL_Rect * r,const Border * b)1192 draw_border(const SDL_Rect *r, const Border *b)
1193 {
1194 SDL_Rect q;
1195 if (b->lft > 0 && r->h>0) {
1196 q = *r; q.w = 1;
1197 SDL_FillRect(TheSurf, &q, Colours[b->colix]);
1198 }
1199 if (b->rgt > 0 && r->w>0 && r->h>0) {
1200 q = *r; q.w = 1; q.x += r->w-1;
1201 SDL_FillRect(TheSurf, &q, Colours[b->colix]);
1202 }
1203 if (b->top > 0 && r->w>0) {
1204 q = *r; q.h = 1;
1205 SDL_FillRect(TheSurf, &q, Colours[b->colix]);
1206 }
1207 if (b->bot > 0 && r->h>0 && r->w>0) {
1208 q = *r; q.h = 1; q.y += r->h-1;
1209 SDL_FillRect(TheSurf, &q, Colours[b->colix]);
1210 }
1211 }
1212
1213 /* Set the view rectangle of a panel. */
1214 static void
recompute_panel_view(Panel * p)1215 recompute_panel_view(Panel *p)
1216 {
1217 p->v = p->r;
1218 p->v.x += abs(p->b.lft);
1219 p->v.y += abs(p->b.bot);
1220 p->v.w = 0;
1221 if (p->r.w > abs(p->b.lft) + abs(p->b.rgt)) {
1222 p->v.w = p->r.w - (abs(p->b.lft) + abs(p->b.rgt));
1223 }
1224 p->v.h = 0;
1225 if (p->r.h > abs(p->b.top) + abs(p->b.bot)) {
1226 p->v.h = p->r.h - (abs(p->b.top) + abs(p->b.bot));
1227 }
1228 }
1229
1230 /* Create a new panel. */
1231 static Panel
new_panel(Sint16 x,Sint16 y,Uint16 w,Uint16 h,Border b)1232 new_panel(Sint16 x, Sint16 y, Uint16 w, Uint16 h, Border b)
1233 {
1234 Panel p;
1235 p.r.x = x;
1236 p.r.y = y;
1237 p.r.w = w;
1238 p.r.h = h;
1239 p.b = b;
1240 recompute_panel_view(&p);
1241 return p;
1242 }
1243
1244 /* Return the "view rectangle" that contains the contents of the panel. */
1245 static const SDL_Rect*
panel_view(const Panel * p)1246 panel_view(const Panel *p)
1247 {
1248 return &p->v;
1249 }
1250
1251 /* Make sure the panel area is updated on the screen. */
1252 static void
refresh_panel(const Panel * p)1253 refresh_panel(const Panel *p)
1254 {
1255 refresh_rect(&p->r);
1256 }
1257
1258 /* Return the size of the panel's view rectangle in columns and rows
1259 * of the current font. */
1260 static void
panel_text_size(const Panel * p,int * cols,int * lines)1261 panel_text_size(const Panel *p, int *cols, int *lines)
1262 {
1263 const SDL_Rect *r = panel_view(p);
1264 if (cols) { *cols = r->w / CurrentFont.w; }
1265 if (lines) { *lines = r->h / CurrentFont.h; }
1266 }
1267
1268 /* Clear a rectangle. */
1269 static void
clear_rect(const SDL_Rect * r)1270 clear_rect(const SDL_Rect *r)
1271 {
1272 SDL_Rect c = *r;
1273 Slock(TheSurf);
1274 SDL_FillRect(TheSurf, &c, Colours[BLACK]);
1275 Sunlock(TheSurf);
1276 }
1277
1278
1279 /* Clear the contents of the panel and draw the border. */
1280 static void
clear_panel(Panel * p)1281 clear_panel(Panel *p)
1282 {
1283 SDL_Rect r = p->r;
1284 SDL_FillRect(TheSurf, &r, Colours[BLACK]);
1285 draw_border(&r, &p->b);
1286 }
1287
1288
1289 /* ------------------------------------------------------------------------
1290 * Generic panel layout (with ideas cheerfully stolen from Andrew Plotkin's
1291 * GLK model.)
1292 *
1293 * Panel are isolated entities and don't know how to react to resizing and
1294 * other layout affecting events. "Layout" structures link panels together
1295 * so that they may be layed out in simple ways. New layouts are created
1296 * by splitting previously defined layouts, in which case the new layout
1297 * takes some part of the space of the splitted layout.
1298 *
1299 * When a layout is split, it is "replaced" by an internally generated
1300 * layout that has the old layout and the new layout as it's children. By
1301 * splitting layouts like this, they form a binary tree with the leaf nodes
1302 * corresponding to actual panels that are drawn onto, and the internal
1303 * nodes to internally generated layout structures (called pair layouts.)
1304 **/
1305
1306 /* Initialise the statically allocated pool of layout structures. */
1307 static void
init_all_layouts(void)1308 init_all_layouts(void)
1309 {
1310 static int done_init = 0;
1311 if (!done_init) {
1312 int i;
1313 memset((void*)all_layouts, 0, MAXLAYOUTS*sizeof(Layout));
1314 layout_free_list = &all_layouts[0];
1315 for (i=0; i<MAXLAYOUTS-1; i++) {
1316 all_layouts[i].a = &all_layouts[i+1];
1317 }
1318 all_layouts[MAXLAYOUTS-1].a = NULL;
1319 done_init = 1;
1320 }
1321 }
1322
1323 /* Default action to take before laying out a layout into a display
1324 * of size w by h pixels. */
1325 static int
default_before_layoutfunc(Layout * a,Uint16 w,Uint16 h)1326 default_before_layoutfunc(Layout *a, Uint16 w, Uint16 h)
1327 {
1328 a = a; w = w; h = h;
1329 return 1;
1330 }
1331
1332 /* Default action to take after laying out. */
1333 static int
default_after_layoutfunc(Panel * p,void * parms)1334 default_after_layoutfunc(Panel *p, void *parms)
1335 {
1336 parms = parms; p = p;
1337 return 1;
1338 }
1339
1340 /* Default redraw only draws the borders of a panel. */
1341 static int
default_redrawfunc(Panel * p,void * parms)1342 default_redrawfunc(Panel *p, void *parms)
1343 {
1344 parms = parms;
1345 draw_border(&p->r, &p->b);
1346 return 1;
1347 }
1348
1349 /* Default clear only clears a panel (and possibly draws borders.) */
1350 static int
default_clearfunc(Panel * p,void * parms)1351 default_clearfunc(Panel *p, void *parms)
1352 {
1353 parms = parms;
1354 clear_panel(p);
1355 return 1;
1356 }
1357
1358 /* Default refresh refreshes the panel. */
1359 static int
default_refreshfunc(Panel * p,void * parms)1360 default_refreshfunc(Panel *p, void *parms)
1361 {
1362 parms = parms;
1363 refresh_panel(p);
1364 return 1;
1365 }
1366
1367 /* Default action to take on closing a panel. */
1368 static void
default_closefunc(void * parms)1369 default_closefunc(void *parms)
1370 {
1371 parms = parms;
1372 }
1373
1374 /* Default action to take when the display mode has changed. */
1375 static void
default_remodefunc(void * parms)1376 default_remodefunc(void *parms)
1377 {
1378 parms = parms;
1379 }
1380
1381 /* Allocate a new layout from the pool of statically allocated layouts. */
1382 static Layout *
alloc_layout(void)1383 alloc_layout(void)
1384 {
1385 Layout *lay;
1386 init_all_layouts();
1387 lay = layout_free_list;
1388 assert(lay && "Ran out of layouts.");
1389 layout_free_list = lay->a;
1390
1391 lay->p = new_panel(0,0, 0,0, noborder);
1392 lay->a = lay->b = NULL;
1393 lay->minw = lay->minh = 0;
1394 lay->maxw = lay->maxh = UNBOUNDED;
1395 lay->before_layout = default_before_layoutfunc;
1396 lay->after_layout = default_after_layoutfunc;
1397 lay->doclear = default_clearfunc;
1398 lay->doredraw = default_redrawfunc;
1399 lay->doclose = default_closefunc;
1400 lay->doremode = default_remodefunc;
1401 lay->dorefresh = default_refreshfunc;
1402 lay->parms = NULL;
1403 lay->parent = NULL;
1404 lay->method = 0;
1405 lay->size = 0;
1406 return lay;
1407 }
1408
1409 /* Allocate a pair-layout (internal layout). */
1410 static Layout *
alloc_pair_layout(void)1411 alloc_pair_layout(void)
1412 {
1413 Layout *pair = alloc_layout();
1414 pair->before_layout = NULL;
1415 pair->after_layout = NULL;
1416 pair->doclear = NULL;
1417 pair->doredraw = NULL;
1418 pair->doclose = NULL;
1419 pair->doremode = NULL;
1420 pair->dorefresh = NULL;
1421 return pair;
1422 }
1423
1424 /* Free a layout. */
1425 static void
free_layout(Layout * lay)1426 free_layout(Layout *lay)
1427 {
1428 if (lay->doclose) { lay->doclose(lay->parms); }
1429 lay->a = layout_free_list;
1430 layout_free_list = lay;
1431 }
1432
1433 /* Saturating addition of width|height bounds. */
1434 static Uint16
add_bounds(Uint32 a,Uint32 b)1435 add_bounds(Uint32 a, Uint32 b)
1436 {
1437 Uint32 c = a + b;
1438 return (Uint16)(c <= UNBOUNDED ? c : UNBOUNDED);
1439 }
1440
1441 #if 0
1442 /* Saturating(?) subtraction of width|height bounds. */
1443 static Uint16
1444 sub_bounds(Uint32 a, Uint32 b)
1445 {
1446 Sint32 c = a - b;
1447 if (a >= UNBOUNDED) { return UNBOUNDED; }
1448 return max(c, 0);
1449 }
1450 #endif
1451
1452 /* Compute the minimum and maximum sizes for all pair layouts bottom-up
1453 * from the bounds of the leaf layouts. */
1454 static void
synthesize_bounds(Layout * pair)1455 synthesize_bounds(Layout *pair)
1456 {
1457 if (!pair->a && !pair->b) {
1458 assert(pair->minw <= pair->maxw);
1459 assert(pair->minh <= pair->maxh);
1460 return; /* not a pair. */
1461 }
1462 assert(pair->a && pair->b);
1463 synthesize_bounds(pair->a);
1464 synthesize_bounds(pair->b);
1465 if (is_horizontal_split(pair->method)) {
1466 pair->minw = add_bounds(pair->a->minw, pair->b->minw);
1467 pair->maxw = add_bounds(pair->a->maxw, pair->b->maxw);
1468 pair->minh = max(pair->a->minh, pair->b->minh);
1469 pair->maxh = max(pair->a->maxh, pair->b->maxh);
1470 } else {
1471 pair->minh = add_bounds(pair->a->minh, pair->b->minh);
1472 pair->maxh = add_bounds(pair->a->maxh, pair->b->maxh);
1473 pair->minw = max(pair->a->minw, pair->b->minw);
1474 pair->maxw = max(pair->a->maxw, pair->b->maxw);
1475 }
1476 assert(pair->minw <= pair->maxw);
1477 assert(pair->minh <= pair->maxh);
1478 }
1479
1480 /* Return a freshly allocated (default) layout with the given
1481 * minimum/maximum bounds and border. */
1482 static Layout *
new_layout(Uint16 minw,Uint16 maxw,Uint16 minh,Uint16 maxh,Border b)1483 new_layout(Uint16 minw, Uint16 maxw,
1484 Uint16 minh, Uint16 maxh,
1485 Border b)
1486 {
1487 Layout *a = alloc_layout();
1488 assert(minw <= maxw);
1489 assert(minh <= maxh);
1490 a->p.b = b;
1491 a->minw = add_bounds(minw, abs(b.lft) + abs(b.rgt));
1492 a->minh = add_bounds(minh, abs(b.top) + abs(b.bot));
1493 a->maxw = add_bounds(maxw, abs(b.lft) + abs(b.rgt));
1494 a->maxh = add_bounds(maxh, abs(b.top) + abs(b.bot));
1495 return a;
1496 }
1497
1498 /* Return the root layout structure of a layout. */
1499 static Layout *
get_root_layout(Layout * a)1500 get_root_layout(Layout *a)
1501 {
1502 while (a->parent) { a = a->parent; }
1503 return a;
1504 }
1505
1506 /* Free all contained layouts bottom-up of a layout, including itself. */
1507 static void
recursive_free_layout(Layout * a)1508 recursive_free_layout(Layout *a)
1509 {
1510 if (a->a) { recursive_free_layout(a->a); }
1511 if (a->b) { recursive_free_layout(a->b); }
1512 free_layout(a);
1513 }
1514
1515 /* Returns the sibling layout of a non-root layout. */
1516 static Layout *
get_sibling_layout(const Layout * a)1517 get_sibling_layout(const Layout *a)
1518 {
1519 if (a->parent == NULL) { return NULL; }
1520 if (a->parent->a != a) { return a->parent->a; }
1521 return a->parent->b;
1522 }
1523
1524 /* Close a layout (and all child layouts). There are three cases
1525 * depending on whether or not the layout's parent needs to be closed
1526 * too (layout A is the one we are closing):
1527 *
1528 * i) ii) O iii) G G
1529 * A -> (nothing) / \ -> B / \ -> / \
1530 * A B ? O ? B
1531 * / \
1532 * A B
1533 */
1534 static void
close_layout(Layout * a)1535 close_layout(Layout *a)
1536 {
1537 if (a->parent != NULL) { /* Need to free a parent? */
1538 Layout *parent = a->parent;
1539 Layout *grandparent = parent->parent;
1540 Layout *b = get_sibling_layout(a);
1541 b->parent = grandparent;
1542 if (grandparent != NULL) { /* Need to replace parent with b? */
1543 if (grandparent->a == parent) {
1544 grandparent->a = b;
1545 } else {
1546 assert(grandparent->b == parent);
1547 grandparent->b = b;
1548 }
1549 }
1550 free_layout(parent);
1551 }
1552 recursive_free_layout(a);
1553 }
1554
1555 /* Return a new layout that splits the given leaf-layout. */
1556 static Layout *
split_layout(Layout * a,Uint32 method,Sint16 size,Uint16 mind,Uint16 maxd,Border border)1557 split_layout(Layout *a, /* layout to split */
1558 Uint32 method, /* split method */
1559 Sint16 size, /* size of split */
1560 Uint16 mind, Uint16 maxd,
1561 Border border) /* border */
1562 {
1563 Layout *b = alloc_layout();
1564 Layout *pair = alloc_pair_layout();
1565 Layout *parent;
1566 b->p.b = border;
1567 assert(!a->a && !a->b && "Can't split a pair layout.");
1568 assert(mind <= maxd);
1569 assert(a->minw <= a->maxw);
1570 assert(a->minh <= a->maxh);
1571
1572 /* Fix parent relations. */
1573 parent = a->parent;
1574 pair->a = a; a->parent = pair;
1575 pair->b = b; b->parent = pair;
1576 pair->parent = parent;
1577 if (parent) {
1578 if (parent->a == a) {
1579 parent->a = pair;
1580 } else {
1581 assert(parent->b == a);
1582 parent->b = pair;
1583 }
1584 }
1585
1586 /* Set split info. */
1587 pair->size = size;
1588 pair->method = method;
1589
1590 /* Set layout bounds. */
1591 if (is_horizontal_split(method)) {
1592 Uint16 d = abs(border.lft) + abs(border.rgt);
1593 b->minh = a->minh;
1594 b->maxh = a->maxh;
1595 b->minw = add_bounds(mind, d);
1596 b->maxw = add_bounds(maxd, d);
1597 } else /* horizontal layout */ {
1598 Uint16 d = abs(border.top) + abs(border.bot);
1599 b->minw = a->minw;
1600 b->maxw = a->maxw;
1601 b->minh = add_bounds(mind, d);
1602 b->maxh = add_bounds(maxd, d);
1603 }
1604 return b;
1605 }
1606
1607 /* Auxiliary function to compute sizes *WA and *WB that use together
1608 * up to W pixels (in width or height), alloting PERCENTAGE of W to
1609 * *WB, but taking note of minimum and maximum bounds for the sizes of
1610 * A and B. */
1611 static void
choose_sizes(Uint32 w,Uint32 percentage,Uint32 amin,Uint32 amax,Uint32 bmin,Uint32 bmax,Uint16 * wa,Uint16 * wb)1612 choose_sizes(
1613 Uint32 w, /* available size. */
1614 Uint32 percentage, /* percentage alloted to B. */
1615 Uint32 amin, Uint32 amax, /* bounds for A. */
1616 Uint32 bmin, Uint32 bmax, /* bounds for B. */
1617 Uint16 *wa, Uint16 *wb) /* result pointers. */
1618 {
1619 if (w > amax + bmax) {
1620 *wa = amax;
1621 *wb = bmax;
1622 } else {
1623 Uint32 w1, w2;
1624 w2 = w * percentage / 100;
1625 w2 = min(w2, bmax);
1626 w2 = max(w2, bmin);
1627
1628 w1 = w - w2;
1629 w1 = min(w1, amax);
1630 w1 = max(w1, amin);
1631 w2 = w - w1;
1632 *wa = w1;
1633 *wb = w2;
1634 }
1635 }
1636
1637 /* Propose a layout for the layout PAIR into the W by H rectangle
1638 * whose top-left corner is at (X,Y), top-down. The minimum and
1639 * maximum bounds for all pair-layouts must have been computed by
1640 * synthesize_bounds() before calling this function. */
1641 static void
propose_layout(Layout * pair,Sint16 x,Sint16 y,Uint16 w,Uint16 h)1642 propose_layout(Layout *pair, Sint16 x, Sint16 y, Uint16 w, Uint16 h)
1643 {
1644 Layout *a = pair->a;
1645 Layout *b = pair->b;
1646 assert(w >= pair->minw);
1647 assert(h >= pair->minh);
1648 w = min(w, pair->maxw);
1649 h = min(h, pair->maxh);
1650 pair->p.r.x = x;
1651 pair->p.r.y = y;
1652 pair->p.r.w = w;
1653 pair->p.r.h = h;
1654 recompute_panel_view(&pair->p);
1655 if (!pair->a && !pair->b) { /* A user's layout. */
1656 return; /* all done! */
1657 } /* else a pair layout */
1658
1659 /* Choose sizes based on the split parameters. */
1660 if (is_horizontal_split(pair->method)) {
1661 choose_sizes(w, pair->size,
1662 a->minw, a->maxw, b->minw, b->maxw,
1663 &a->p.r.w, &b->p.r.w);
1664 a->p.r.h = b->p.r.h = h;
1665 a->p.r.h = min(a->p.r.h, a->maxh); a->p.r.h = max(a->p.r.h, a->minh);
1666 b->p.r.h = min(b->p.r.h, b->maxh); b->p.r.h = max(b->p.r.h, b->minh);
1667 } else {
1668 choose_sizes(h, pair->size,
1669 a->minh, a->maxh, b->minh, b->maxh,
1670 &a->p.r.h, &b->p.r.h);
1671 a->p.r.w = b->p.r.w = w;
1672 a->p.r.w = min(a->p.r.w, a->maxw); a->p.r.w = max(a->p.r.w, a->minw);
1673 b->p.r.w = min(b->p.r.w, b->maxw); b->p.r.w = max(b->p.r.w, b->minw);
1674 }
1675
1676 /* Make sure the layout algorithm respects all bounds. */
1677 assert(a->p.r.w >= a->minw);
1678 assert(a->p.r.w <= a->maxw);
1679 assert(b->p.r.w >= b->minw);
1680 assert(b->p.r.w <= b->maxw);
1681 assert(a->p.r.h >= a->minh);
1682 assert(a->p.r.h <= a->maxh);
1683 assert(b->p.r.h >= b->minh);
1684 assert(b->p.r.h <= b->maxh);
1685 assert(a->p.r.w <= w);
1686 assert(b->p.r.w <= w);
1687 assert(a->p.r.h <= h);
1688 assert(b->p.r.h <= h);
1689 if (is_horizontal_split(pair->method)) {
1690 assert(a->p.r.w + b->p.r.w <= w);
1691 } else {
1692 assert(a->p.r.h + b->p.r.h <= h);
1693 }
1694
1695 /* Set positions based on the method of split and sizes. */
1696 switch(pair->method) {
1697 case BELOWS: /* b below a */
1698 a->p.r.x = x;
1699 a->p.r.y = y;
1700 b->p.r.x = x;
1701 b->p.r.y = y + a->p.r.h;
1702 break;
1703 case ABOVES: /* b above a */
1704 b->p.r.x = x;
1705 b->p.r.y = y;
1706 a->p.r.x = x;
1707 a->p.r.y = y + b->p.r.h;
1708 break;
1709 case LEFTS: /* b to left of a */
1710 b->p.r.x = x;
1711 b->p.r.y = y;
1712 a->p.r.x = x + b->p.r.w;
1713 a->p.r.y = y;
1714 break;
1715 case RIGHTS: /* b to right of a */
1716 a->p.r.x = x;
1717 a->p.r.y = y;
1718 b->p.r.x = x + a->p.r.w;
1719 b->p.r.y = y;
1720 break;
1721 }
1722 recompute_panel_view(&a->p);
1723 recompute_panel_view(&b->p);
1724
1725 /* Propose layouts for children. */
1726 propose_layout(pair->a, a->p.r.x, a->p.r.y, a->p.r.w, a->p.r.h);
1727 propose_layout(pair->b, b->p.r.x, b->p.r.y, b->p.r.w, b->p.r.h);
1728 }
1729
1730 /* Redraw all borders of layouts below (and including) layout A. Calls
1731 * the doclear() callback of layouts. */
1732 static void
redraw_layout_borders(Layout * a)1733 redraw_layout_borders(Layout *a)
1734 {
1735 draw_border(&a->p.r, &a->p.b);
1736 if (a->a) { redraw_layout_borders(a->a); }
1737 if (a->b) { redraw_layout_borders(a->b); }
1738 }
1739
1740 /* Clear all layouts below (and including) layout A. Calls the
1741 * doclear() callback of layouts. */
1742 static void
clear_layout(Layout * a)1743 clear_layout(Layout *a)
1744 {
1745 if (a->doclear) { a->doclear(&a->p, a->parms); }
1746 if (a->a) { clear_layout(a->a); }
1747 if (a->b) { clear_layout(a->b); }
1748 }
1749
1750 /* Call the doremode() call backs of layouts. These should be called
1751 * whenever the display mode changes. */
1752 static void
remode_layout(Layout * a)1753 remode_layout(Layout *a)
1754 {
1755 if (a->a) { remode_layout(a->a); }
1756 if (a->b) { remode_layout(a->b); }
1757 if (a->doremode) { a->doremode(a->parms); }
1758 }
1759
1760 /* Call the after_layout() call back of layouts. The first one that
1761 * returns FALSE causes a quick return, so some call backs might not
1762 * be called. */
1763 static int
callback_after_layouts(Layout * a)1764 callback_after_layouts(Layout *a)
1765 {
1766 return (!a->a || callback_after_layouts(a->a))
1767 && (!a->b || callback_after_layouts(a->b))
1768 && (!a->after_layout || a->after_layout(&a->p, a->parms));
1769 }
1770
1771 /* Call the before_layout() call backs of layouts below (and
1772 * including) A. The first callback returning FALSE causes a quick
1773 * return, so some callbacks might not be called. */
1774 static int
callback_before_layouts(Layout * a,Uint16 w,Uint16 h)1775 callback_before_layouts(Layout *a, Uint16 w, Uint16 h)
1776 {
1777 return (!a->a || callback_before_layouts(a->a, w, h))
1778 && (!a->b || callback_before_layouts(a->b, w, h))
1779 && (!a->before_layout || a->before_layout(a, w, h));
1780 }
1781
1782 /* Redraw a layout and all its children. */
1783 static void
redraw_layout(Layout * a)1784 redraw_layout(Layout *a)
1785 {
1786 if (a->doredraw) { (void)a->doredraw(&a->p, a->parms); }
1787 if (a->a) { redraw_layout(a->a); }
1788 if (a->b) { redraw_layout(a->b); }
1789 }
1790
1791 /* Refresh the area on the display that the layout occupies. */
1792 static void
refresh_layout(Layout * a)1793 refresh_layout(Layout *a)
1794 {
1795 if (a->a) { refresh_layout(a->a); }
1796 if (a->b) { refresh_layout(a->b); }
1797 if (a->dorefresh) { (void)a->dorefresh(&a->p, a->parms); }
1798 }
1799
1800 /* Attempt to recursively layout all the layouts below (and including)
1801 * the ROOT layout. Returns TRUE on success, FALSE on failure. */
1802 static int
layout(Layout * root,Sint16 x,Sint16 y,Uint16 w,Uint16 h)1803 layout(Layout *root, Sint16 x, Sint16 y, Uint16 w, Uint16 h)
1804 {
1805 /* Let the user's layouts adjust themselves to the new width and height
1806 * of the display.
1807 */
1808 if (!callback_before_layouts(root, w, h)) {
1809 return 0;
1810 }
1811
1812 /* Check that we have enough space. */
1813 synthesize_bounds(root);
1814 if (w < root->minw || h < root->minh)
1815 {
1816 return 0;
1817 }
1818
1819 /* Wunderbar. Now compute a layout proposal into the layout structures
1820 * top-down. */
1821 propose_layout(root, x, y, w, h);
1822
1823 /* Phew. Now run the layout proposal past the user call-backs to see
1824 * if they agree. */
1825 return callback_after_layouts(root);
1826 }
1827
1828 /* ------------------------------------------------------------------------
1829 * TextOutputs
1830 *
1831 * TextOutputs are Layouts that provide textual output in the current
1832 * font. They have a cursor position, know how to wrap long lines
1833 * when necessary, and attempt to refresh only the parts of their
1834 * layout's panel that have been drawn on. */
1835
1836 /* Return a TextOutput to the statically allocated pool of free TextOutputs. */
1837 static void
free_text_output(TextOutput * t)1838 free_text_output(TextOutput *t)
1839 {
1840 t->next = textoutput_free_list;
1841 textoutput_free_list = t;
1842 }
1843
1844 /* Initialise the pool of free TextOutputs. */
1845 static void
init_all_text_outputs(void)1846 init_all_text_outputs(void)
1847 {
1848 static int done_init = 0;
1849 if (!done_init) {
1850 int k;
1851 textoutput_free_list = NULL;
1852 for (k=MAXTEXTOUTPUTS-1; k>=0; --k) {
1853 free_text_output(&all_textoutputs[k]);
1854 }
1855 done_init = 1;
1856 }
1857 }
1858
1859 #if 0
1860 static void
1861 print_rect(const char *msg, const SDL_Rect *r)
1862 {
1863 printf("%s %d,%d,%d,%d\n", msg, r->x, r->y, r->w, r->h);
1864 }
1865 #endif
1866
1867 static int
doclear_textoutput(Panel * p,void * parms)1868 doclear_textoutput(Panel *p, void *parms)
1869 {
1870 TextOutput *t = (TextOutput*)parms;
1871 /* clear_panel(p); */
1872 clear_rect(&t->drawn);
1873 #if 0
1874 print_rect("cle", &t->drawn);
1875 #endif
1876 t->refresh = t->drawn; /* Set refresh rectangle. */
1877 t->x = t->y = 0; /* Set point. */
1878 t->drawn.x = p->r.x;
1879 t->drawn.y = p->r.y;
1880 t->drawn.w = t->drawn.h = 0;
1881 return 1;
1882 }
1883
1884 static int
after_layout_textoutput(Panel * p,void * parms)1885 after_layout_textoutput(Panel *p, void *parms)
1886 {
1887 TextOutput *t = (TextOutput*)parms;
1888 t->refresh.w = t->refresh.h = 0;
1889 t->drawn = p->r;
1890 return 1;
1891 }
1892
1893 static void
doclose_textoutput(void * parms)1894 doclose_textoutput(void *parms)
1895 {
1896 TextOutput *t = (TextOutput *)parms;
1897 free_text_output(t);
1898 }
1899
1900 static int
dorefresh_textoutput(Panel * p,void * parms)1901 dorefresh_textoutput(Panel *p, void *parms)
1902 {
1903 TextOutput *t = (TextOutput *)parms;
1904 p=p;
1905 if (t->refresh.w && t->refresh.h) {
1906 #if 0
1907 print_rect("ref", &t->refresh);
1908 #endif
1909 refresh_rect(&t->refresh);
1910 }
1911 t->refresh = t->layout->p.r;
1912 t->refresh.w = t->refresh.h = 0;
1913 return 1;
1914 }
1915
1916 static TextOutput *
alloc_text_output(Layout * a)1917 alloc_text_output(Layout *a)
1918 {
1919 TextOutput *t;
1920 init_all_text_outputs();
1921 t = textoutput_free_list;
1922 assert(t && "Ran out of text outputs");
1923 textoutput_free_list = t->next;
1924 memset((void*)t, 0, sizeof(TextOutput));
1925 t->layout = a;
1926 a->parms = t;
1927 a->after_layout = after_layout_textoutput;
1928 a->doclose = doclose_textoutput;
1929 a->doredraw = doclear_textoutput;
1930 a->doclear = doclear_textoutput;
1931 a->dorefresh = dorefresh_textoutput;
1932 t->x = t->y = 0;
1933 t->refresh.x = a->p.r.x;
1934 t->refresh.y = a->p.r.y;
1935 t->refresh.w = t->refresh.h = 0;
1936 t->drawn = t->refresh;
1937 return t;
1938 }
1939
1940 static void
clear_to_eol(TextOutput * t)1941 clear_to_eol(TextOutput *t)
1942 {
1943 const SDL_Rect *r = panel_view(&t->layout->p);
1944 SDL_Rect v;
1945 v.x = r->x + t->x;
1946 v.y = r->y + t->y;
1947 v.h = CurrentFont.h;
1948 v.w = r->w;
1949 clip_rect(r, &v);
1950 Slock(TheSurf);
1951 SDL_FillRect(TheSurf, &v, Colours[BLACK]);
1952 Sunlock(TheSurf);
1953 refresh_rect(&v);
1954 }
1955
1956 static void
cr(TextOutput * t)1957 cr(TextOutput *t)
1958 {
1959 const SDL_Rect *r = panel_view(&t->layout->p);
1960 t->x = 0;
1961 /* Do we need to scroll up? */
1962 if (t->y+2*CurrentFont.h <= r->h) {
1963 /* No scrolling. Move to the next line. */
1964 t->y += CurrentFont.h;
1965 clear_to_eol(t);
1966 } else {
1967 /* Scroll the panel up so that at least one more line fits and
1968 * update the refresh rectangle to contain all of the panel.
1969 * */
1970 #if 0
1971 /* This version scrolls the minimum amount necessary to allow
1972 * exactly one more line, but it turns out that that creates
1973 * jerky and ugly displays. i.e. it attempts to keep the
1974 * bottom line aligned with the panel's bottom border.
1975 * */
1976 int fh = CurrentFont.h;
1977 int ofs1 = r->h - (t->y+fh);
1978 int ofs2 = fh - ofs1;
1979 SDL_Rect src, dst;
1980
1981 src = *r; /* source rect */
1982 src.y += ofs2;
1983 src.h -= ofs2;
1984 clip_rect(&t->drawn, &src);
1985
1986 dst = src; /* dest rec. */
1987 dst.y -= ofs2;
1988
1989 t->y = r->h - fh; /* update point. */
1990
1991 SDL_BlitSurface(TheSurf, &src, TheSurf, &dst);
1992 join_rect(&dst, &t->refresh);
1993 join_rect(&dst, &t->drawn);
1994 #else
1995 /* This version scrolls up enough so that the top line is
1996 * always aligned at the panel top border. It leaves some unused
1997 * space at the bottom, but that's probably ok. */
1998 int fh = CurrentFont.h;
1999 SDL_Rect src, dst;
2000 src = *r;
2001 src.y += fh;
2002 src.h -= fh;
2003 clip_rect(&t->drawn, &src);
2004 dst = src;
2005 dst.y -= fh;
2006
2007 SDL_BlitSurface(TheSurf, &src, TheSurf, &dst);
2008 join_rect(&dst, &t->refresh);
2009 join_rect(&dst, &t->drawn);
2010 #endif
2011 clear_to_eol(t);
2012 }
2013 }
2014
2015 static void
emit(TextOutput * t,unsigned char c,int colix)2016 emit(TextOutput *t, unsigned char c, int colix)
2017 {
2018 const SDL_Rect *v = panel_view(&t->layout->p);
2019 if (c=='\n') { cr(t); return; }
2020
2021 /* Do we need to wrap? */
2022 if (t->x+CurrentFont.w > v->w) {
2023 cr(t);
2024 }
2025 {
2026 SDL_Rect r;
2027 r.x = v->x + t->x; r.y = v->y + t->y;
2028 r.h = CurrentFont.h; r.w = CurrentFont.w;
2029 clip_rect(v, &r);
2030 outcharxy(c, r.x, r.y, colix, &r);
2031 t->x += CurrentFont.w;
2032 join_rect(&r, &t->refresh);
2033 join_rect(&r, &t->drawn);
2034 }
2035 }
2036
2037 static void
backspace(TextOutput * t)2038 backspace(TextOutput *t)
2039 {
2040 t->x -= CurrentFont.w;
2041 if (t->x<0) {
2042 t->x = 0;
2043 }
2044 }
2045
2046 static void
backchar(TextOutput * t)2047 backchar(TextOutput *t)
2048 {
2049 if (t->x >= CurrentFont.w) {
2050 t->x -= CurrentFont.w;
2051 } else {
2052 t->x = 0;
2053 if (t->y >= CurrentFont.h) {
2054 int cols;
2055 t->y -= CurrentFont.h;
2056 panel_text_size(&t->layout->p, &cols, NULL);
2057 t->x = max(0,(cols-1))*CurrentFont.w;
2058 }
2059 }
2060 }
2061
2062 static void
delchar(TextOutput * t)2063 delchar(TextOutput *t)
2064 {
2065 backchar(t);
2066 emit(t, ' ', BLACK);
2067 backspace(t);
2068 }
2069
2070 static void
type(TextOutput * t,const char * str,int colix)2071 type(TextOutput *t, const char *str, int colix)
2072 {
2073 while (*str) {
2074 emit(t, *str, colix);
2075 if (*str==' ' && t->x <= CurrentFont.w) {
2076 /* After wrapping text, don't emit a space as the very
2077 first character of a line. */
2078 backspace(t);
2079 }
2080 str++;
2081 }
2082 }
2083
2084
2085 static void
type_nowrap(TextOutput * t,const char * str,int colix)2086 type_nowrap(TextOutput *t, const char *str, int colix)
2087 {
2088 const SDL_Rect *v = panel_view(&t->layout->p);
2089 while (*str) {
2090 SDL_Rect r;
2091 r.x = v->x + t->x;
2092 r.y = v->y + t->y;
2093 r.w = CurrentFont.w;
2094 r.h = CurrentFont.h;
2095 clip_rect(v, &r);
2096 outcharxy(*str, r.x, r.y, colix, &r);
2097 t->x += CurrentFont.w;
2098 join_rect(&r, &t->refresh);
2099 join_rect(&r, &t->drawn);
2100 str++;
2101 }
2102 }
2103
2104
2105
2106 /* ------------------------------------------------------------------------
2107 * TextInputs
2108 *
2109 * TextInputs supplement Layouts by providing text input facilities.
2110 * They don't do any event handling themselves, but track the internal
2111 * state of a text panel that an event handler can modify through a
2112 * narrowish interface. An event handler should always activate() a
2113 * TextInput when it is starting to gather input, and deactivate() it
2114 * when input has been gathered. The handler is also responsible for
2115 * refreshing the TextInput's Layout at convenient moments by calling
2116 * the refresh_layout() function on it's layout.
2117 *
2118 * A TextInput has two states:
2119 *
2120 * active : When set, the TextInput is all geared up for receiving
2121 * characters from the user (event handler). When clear,
2122 * the TextInput pretends it is an empty panel. This
2123 * variable may only be modified by calls to activate(),
2124 * deactivate(), and finish_input().
2125 *
2126 * and a handful of minor boolean state variables:
2127 *
2128 * has_focus : When set and the TextInput is active, draw a cursor
2129 * character to indicate that to the user. This variable
2130 * is controlled through the functions give_focus() and
2131 * take_focus().
2132 *
2133 * needs_prompting: If set, the next time the TextInput is activated,
2134 * it will redraw the prompt and any input it has
2135 * gathered into its input buffer. This flag should be
2136 * set at any time that the TextInput's visible area is
2137 * externally modified (by resize events or whatever.)
2138 **/
2139
2140 /* Return a TextInput to the statically allocated pool of free TextInputs. */
2141 static void
free_text_input(TextInput * t)2142 free_text_input(TextInput *t)
2143 {
2144 t->next = textinput_free_list;
2145 textinput_free_list = t;
2146 }
2147
2148 /* Initialise the pool of free TextInputs. */
2149 static void
init_all_text_inputs(void)2150 init_all_text_inputs(void)
2151 {
2152 static int done_init = 0;
2153 if (!done_init) {
2154 int k;
2155 textinput_free_list = NULL;
2156 for (k=MAXTEXTINPUTS-1; k>=0; --k) {
2157 free_text_input(&all_textinputs[k]);
2158 }
2159 done_init = 1;
2160 }
2161 }
2162
2163 /* Draw the cursor in a TextInput if it is active and has focus. */
2164 static void
cursoron(TextInput * t)2165 cursoron(TextInput *t)
2166 {
2167 if (t->has_focus && t->active) {
2168 emit(t->out, '_', t->colix);
2169 backspace(t->out);
2170 }
2171 }
2172
2173 /* Clear the cursor in a TextInput if it is active and has focus. */
2174 static void
cursoroff(TextInput * t)2175 cursoroff(TextInput *t)
2176 {
2177 if (t->has_focus && t->active) {
2178 emit(t->out, ' ', t->colix);
2179 backspace(t->out);
2180 }
2181 }
2182
2183 /* Layout doredraw() callback redraws a text input if it is active. */
2184 static int
doredraw_textinput(Panel * p,void * parms)2185 doredraw_textinput(Panel *p, void *parms)
2186 {
2187 TextInput *t = (TextInput*)parms;
2188 doclear_textoutput(p, t->out);
2189 if (t->active) {
2190 type(t->out, t->prompt, t->colix);
2191 type(t->out, t->buf, t->colix);
2192 cursoron(t);
2193 }
2194 return 1;
2195 }
2196
2197 static int
after_layout_textinput(Panel * p,void * parms)2198 after_layout_textinput(Panel *p, void *parms)
2199 {
2200 TextInput *t = (TextInput*)parms;
2201 return after_layout_textoutput(p, t->out);
2202 }
2203
2204 /* Layout dorefresh() callback refreshes the layout only when it has been
2205 drawn into. */
2206 static int
dorefresh_textinput(Panel * p,void * parms)2207 dorefresh_textinput(Panel *p, void *parms)
2208 {
2209 TextInput *t = (TextInput*)parms;
2210 dorefresh_textoutput(p, t->out);
2211 return 1;
2212 }
2213
2214
2215 /* Layout doclose() callback frees a related TextInput. */
2216 static void
doclose_textinput(void * parms)2217 doclose_textinput(void *parms)
2218 {
2219 TextInput *t = (TextInput*)parms;
2220 doclose_textoutput(t->out);
2221 free_text_input(t);
2222 }
2223
2224 /* Allocate a new TextInput and relate it to the given Layout. */
2225 static TextInput *
alloc_text_input(Layout * a)2226 alloc_text_input(Layout *a)
2227 {
2228 TextInput *t;
2229 init_all_text_inputs();
2230 t = textinput_free_list;
2231 assert(t && "Ran out of text input panels");
2232 textinput_free_list = t->next;
2233 memset((void*)t, 0, sizeof(TextInput));
2234 t->out = alloc_text_output(a);
2235 t->colix = LIGHTGREY;
2236 t->layout = a;
2237 a->parms = t;
2238 a->doclose = doclose_textinput;
2239 a->doredraw = doredraw_textinput;
2240 a->doclear = doredraw_textinput;
2241 a->dorefresh = dorefresh_textinput;
2242 a->after_layout = after_layout_textinput;
2243 t->active = 0;
2244 t->has_focus = 0;
2245 t->needs_prompting = 1;
2246 return t;
2247 }
2248
2249 /* Activate a TextInput for receiving input. */
2250 static void
activate(TextInput * t)2251 activate(TextInput *t)
2252 {
2253 if (t->needs_prompting) {
2254 type(t->out, t->prompt, t->colix);
2255 type(t->out, t->buf, t->colix);
2256 }
2257 t->active = 1;
2258 t->needs_prompting = 0;
2259 cursoron(t);
2260 }
2261
2262 /* Deactivate a TextInput. */
2263 static void
deactivate(TextInput * t)2264 deactivate(TextInput *t)
2265 {
2266 t->active = 0;
2267 }
2268
2269 /* Is the TextInput receiving input? */
2270 static int
is_active(TextInput * t)2271 is_active(TextInput *t)
2272 {
2273 return t->active;
2274 }
2275
2276 /* Give focus. */
2277 static void
give_focus(TextInput * t)2278 give_focus(TextInput *t)
2279 {
2280 t->has_focus = 1;
2281 cursoron(t);
2282 }
2283
2284 /* Remove focus. */
2285 static void
take_focus(TextInput * t)2286 take_focus(TextInput *t)
2287 {
2288 cursoroff(t);
2289 t->has_focus = 0;
2290 }
2291
2292 /* Set the prompt string of a TextInput. */
2293 static void
set_prompt(TextInput * t,const char * prompt)2294 set_prompt(TextInput *t, const char *prompt)
2295 {
2296 xstrncpy(t->prompt, prompt, MAXALLCHAR);
2297 }
2298
2299 /* Add a character to a TextInput. */
2300 static void
add_char(TextInput * t,char c)2301 add_char(TextInput *t, char c)
2302 {
2303 if (!is_active(t)) { return; }
2304 if (t->len+1 < MAXALLCHAR) {
2305 t->buf[t->len++] = c;
2306 t->buf[t->len] = 0;
2307 if (c=='\n') { cursoroff(t); } /* Remove lingering cursor. */
2308 emit(t->out, c, t->colix);
2309 if (c!='\n') { cursoron(t); }
2310 }
2311 }
2312
2313 /* Erase the last character of a TextInput. */
2314 static void
del_char(TextInput * t)2315 del_char(TextInput *t)
2316 {
2317 if (!is_active(t)) { return; }
2318 if (t->len>0) {
2319 t->buf[--t->len] = 0;
2320 cursoroff(t);
2321 delchar(t->out);
2322 cursoron(t);
2323 }
2324 }
2325
2326 /* Stop receiving input into the TextInput and fetch the input gathered
2327 * so far. */
2328 static void
finish_input(TextInput * t,char * buf,size_t maxbuf)2329 finish_input(TextInput *t, char *buf, size_t maxbuf)
2330 {
2331 t->buf[t->len] = 0;
2332 xstrncpy(buf, t->buf, maxbuf);
2333 cursoroff(t);
2334 deactivate(t);
2335 t->needs_prompting = 1;
2336 t->buf[0] = 0;
2337 t->len = 0;
2338 }
2339
2340 /* Erase the input characters on the screen up to the prompt. NB: This
2341 * does not throw away gathered input or deactivate the TextInput. */
2342 static void
hide_input(TextInput * t)2343 hide_input(TextInput *t)
2344 {
2345 size_t i;
2346 if (!is_active(t)) { return; }
2347 cursoroff(t);
2348 for (i=0; i<t->len; i++) {
2349 delchar(t->out);
2350 }
2351 cursoron(t);
2352 }
2353
2354 static void
reset_input(TextInput * t,const char * s)2355 reset_input(TextInput *t, const char *s)
2356 {
2357 if (!is_active(t)) { return; }
2358 hide_input(t);
2359 t->buf[0] = 0;
2360 t->len = 0;
2361 while (s && *s) { add_char(t, *s++); }
2362 }
2363
2364 /* ------------------------------------------------------------------------
2365 * Core display layout
2366 */
2367
2368 /* Return the size of a cell in pixels. */
2369 static Uint32
get_cell_size(const CellInfo * c)2370 get_cell_size(const CellInfo *c)
2371 {
2372 return 2*c->box_size + c->interbox_space + c->intercell_space;
2373 }
2374
2375 /* Draw a cell-box at the given address in the given colour. Bits TL
2376 * (top-left), TR (top-right), BL (bottom-left), BR (bottom-right) of BOXES
2377 * control which boxes are drawn in the cell. Additionally it updates the
2378 * core colour buffer in the CoreArena. */
2379 static void
display_box(int addr,Uint32 boxes,int colix)2380 display_box(int addr, Uint32 boxes, int colix)
2381 {
2382 SDL_Rect dst;
2383 SDL_Surface *boxsurf;
2384
2385 if (colix != 0) {
2386 /* Colour core colour buffer and figure out which boxes need
2387 * colouring on the display. */
2388 #if 1
2389 /* This version checks whether or not the box needs to be
2390 * blitted. Doesn't work when resizing because then the boxes
2391 * always needs to be blitted so it's not used. */
2392 #define check_box(mask, ofs)\
2393 if (boxes & (mask)) {\
2394 if (CoreArena.core[4*addr+(ofs)] != colix) {\
2395 CoreArena.core[4*addr+(ofs)] = colix;\
2396 } else {\
2397 boxes ^= (mask);\
2398 }\
2399 }
2400 #else
2401 #define check_box(mask, ofs)\
2402 if (boxes & (mask)) { CoreArena.core[4*addr+(ofs)] = colix; }
2403 #endif
2404 check_box(TL, 0);
2405 check_box(TR, 1);
2406 check_box(BL, 2);
2407 check_box(BR, 3);
2408
2409 /* If TL=0x00000001, TR=0x00000100, BL=0x00010000, BR=0x01000000
2410 * and CoreArena.core[] is an Uint32 array, then this might be faster
2411 * since only DWORD's are store-forwaded(?):
2412 *
2413 * CoreArena.core[addr] = (CoreArena.core[addr]
2414 * & ((boxes ^ (TL|TR|BL|BR)) * 0xFF)
2415 * ) | (boxes * colix);
2416 */
2417 if (boxes) {
2418 /* Set destination rectangle. */
2419 dst.x = xkoord(addr);
2420 dst.y = ykoord(addr);
2421 /* dst.w = dst.h = CellSize; */ /* ignored by SDL_BlitSurface().*/
2422
2423 /* Get source surface. */
2424 boxsurf = BlitBoxes[colix][boxes];
2425
2426 /* Blit it! Note: requires colour keying to select area to blit.*/
2427 SDL_BlitSurface(boxsurf, NULL, TheSurf, &dst);
2428 }
2429 }
2430 }
2431
2432 /* Decrease the parameter cell size parameters a bit. This is used to find a
2433 * suitable cell size that fits the screen. Returns a flag that is zero when
2434 * the cell size can't be decreased any more, and if it can. */
2435 static int
decr_cell_size(CellInfo * c)2436 decr_cell_size(CellInfo *c)
2437 {
2438 int success=1;
2439 if (c->interbox_space > 1) {
2440 /* Try to keep boxes further apart than one pixel. */
2441 --c->interbox_space;
2442 } else if (c->intercell_space > 1) {
2443 /* Try to keep cells further apart than boxes. */
2444 --c->intercell_space;
2445 } else if (c->interbox_space > 0) {
2446 /* Give in and make boxes touch each other. */
2447 c->interbox_space = 0;
2448 } else if (c->intercell_space >= 1) {
2449 /* Give in and make cells and boxes the same distance apart. */
2450 c->intercell_space = 0;
2451 } else if (c->box_size>1) {
2452 /* Try to keep boxes larger than one pixel */
2453 --c->box_size;
2454 if (c->intercell_space==0) {
2455 c->intercell_space = 1;
2456 }
2457 } else {
2458 assert(c->box_size == 1);
2459 assert(c->interbox_space == 0);
2460 assert(c->intercell_space == 0);
2461 success = 0;
2462 }
2463 return success;
2464 }
2465
2466 /* Compute the maximum and minimum height of the core display panel
2467 * based on the available width. */
2468 static int
before_layout_arena(Layout * layout,Uint16 w,Uint16 h)2469 before_layout_arena(Layout *layout, Uint16 w, Uint16 h)
2470 {
2471 Panel *p = &layout->p;
2472 Arena *a = (Arena*)(layout->parms);
2473 Uint32 real_w = max(w, layout->minw) - abs(p->b.lft) - abs(p->b.rgt);
2474 Uint32 cellsperline;
2475
2476 h = h; /* unused. */
2477
2478 cellsperline = real_w/2; /* 2 = minimum cell size. */
2479 layout->minh = 2*(a->coresize + cellsperline-1)/cellsperline;
2480 layout->minh += abs(p->b.top) + abs(p->b.bot);
2481
2482 layout->maxh = max(layout->maxh, layout->minh);
2483 return 1;
2484 }
2485
2486 /* After the core panel has been laid out, find a cell size that will
2487 * fit into the alotted space. Also set up related globals. Provided
2488 * the panel is large enough, this should always succeed. */
2489 static int
after_layout_arena(Panel * p,void * parms)2490 after_layout_arena(Panel *p, void *parms)
2491 {
2492 const SDL_Rect *r = panel_view(p);
2493 Arena *a = (Arena*)parms;
2494 int failed = 1;
2495 Uint32 rows, cellw, cellsperline;
2496
2497 /* Find desired cell size. */
2498 a->cell = a->desired_cell;
2499 do {
2500 cellw = get_cell_size(&a->cell);
2501 cellsperline = r->w / cellw;
2502 rows = (a->coresize + cellsperline-1)/cellsperline;
2503 #if 0
2504 fprintf(stderr, "{ %d, %d, %d } = %d/%d\n", a->cell.box_size,
2505 a->cell.interbox_space, a->cell.intercell_space,
2506 rows*cellw, r->h);
2507 #endif
2508 if (rows * cellw <= r->h) {
2509 failed = 0;
2510 }
2511 } while(failed && decr_cell_size(&a->cell));
2512
2513 #if 0
2514 fprintf(stderr, "{ %d, %d, %d } = %d/%d\n", a->cell.box_size,
2515 a->cell.interbox_space, a->cell.intercell_space,
2516 rows*cellw, r->h);
2517 #endif
2518 /* Set up related globals. */
2519 ArenaX = r->x;
2520 ArenaY = r->y;
2521 ArenaX += (r->w - cellw*cellsperline + a->cell.intercell_space)/2;
2522 ArenaY += (r->h - cellw*rows + a->cell.intercell_space)/2;
2523 CellSize = get_cell_size(&a->cell);
2524 CellsPerLine = r->w/CellSize;
2525
2526 /* If there's wasted horizontal space in the arena, adjust the
2527 * maximum height and fail this layout. The layout algorithm
2528 * should try again and get it right. */
2529 if (r->h > rows*cellw + a->cell.intercell_space
2530 + abs(p->b.top) + abs(p->b.bot))
2531 {
2532 a->layout->maxh = a->cell.intercell_space + rows*cellw;
2533 a->layout->maxh += abs(p->b.top) + abs(p->b.bot);
2534 a->layout->minh = min(a->layout->minh, a->layout->maxh);
2535 failed = 1;
2536 }
2537
2538 return !failed;
2539 }
2540
2541 /* Layout doredraw() callback redraws the core panel from the core colour
2542 * buffer. */
2543 static int
doredraw_arena(Panel * p,void * parms)2544 doredraw_arena(Panel *p, void *parms)
2545 {
2546 Arena *a = (Arena*)parms;
2547 Uint32 addr;
2548 clear_panel(p);
2549 for (addr=0; addr<a->coresize; addr++) {
2550 int i;
2551 Uint32 mask = 1;
2552 for (i=0; i<4; i++) {
2553 unsigned char col = a->core[4*addr+i];
2554 a->core[4*addr + i] = 0;
2555 display_box(addr, mask, col);
2556 mask <<= 1;
2557 }
2558 }
2559 return 1;
2560 }
2561
2562 /* Layout doclear() callback clears the arena and core colour buffer. */
2563 static int
doclear_arena(Panel * p,void * parms)2564 doclear_arena(Panel *p, void *parms)
2565 {
2566 Arena *a = (Arena*)parms;
2567 a = a;
2568 clear_panel(p);
2569 memset((void*)a->core, 0, sizeof(unsigned char)*4*a->coresize);
2570 return 1;
2571 }
2572
2573 /* Free the blit boxes array. */
2574 static void
free_blitboxes(Arena * a)2575 free_blitboxes(Arena *a)
2576 {
2577 int colix, boxes;
2578 if (a->blitboxes==NULL) { return; }
2579 for (colix=0; colix<NCOLOURS; colix++) {
2580 if (a->blitboxes[colix]) {
2581 for (boxes = 0; boxes < 16; boxes++) {
2582 SDL_Surface *s = a->blitboxes[colix][boxes];
2583 if (s) {
2584 SDL_FreeSurface(s);
2585 a->blitboxes[colix][boxes] = NULL;
2586 }
2587 }
2588 FREE(a->blitboxes[colix]);
2589 }
2590 }
2591 FREE(a->blitboxes);
2592 a->blitboxes = NULL;
2593 }
2594
2595 /* Fill a square box of a surface with a colour. */
2596 static void
fill_box(SDL_Surface * s,Sint16 x,Sint16 y,Uint16 w,Uint16 h,Uint32 col)2597 fill_box(SDL_Surface *s, Sint16 x, Sint16 y, Uint16 w, Uint16 h, Uint32 col)
2598 {
2599 SDL_Rect r;
2600 r.x = x; r.y = y; r.w = w; r.h = h;
2601 if (-1==SDL_FillRect(s, &r, col)) {
2602 exit_panic(errSpriteFill, SDL_GetError());
2603 }
2604 }
2605
2606 /* Predraw a blitbox of a given colour and pattern. */
2607 static void
make_blitbox(Arena * a,int colix,Uint32 boxes)2608 make_blitbox(Arena *a, int colix, Uint32 boxes)
2609 {
2610 SDL_Surface *sprite, *temp;
2611 Uint32 key, col;
2612 int cellsize = get_cell_size(&a->cell);
2613 int bs = a->cell.box_size;
2614 int ibsp = a->cell.interbox_space;
2615
2616 sprite = SDL_CreateRGBSurface(SDL_ANYFORMAT, cellsize, cellsize,
2617 TheSurf->format->BitsPerPixel,
2618 TheSurf->format->Rmask,
2619 TheSurf->format->Gmask,
2620 TheSurf->format->Bmask,
2621 0); /* no alpha, we want colorkey blits */
2622 if (sprite == NULL) {
2623 exit_panic(errSpriteSurf, SDL_GetError());
2624 }
2625
2626 /* Set colorkey (=black) for the sprite. */
2627 key = SDL_MapRGB(sprite->format, 0, 0, 0);
2628 if (-1==SDL_SetColorKey(sprite, SDL_SRCCOLORKEY | SDL_RLEACCEL, key)) {
2629 exit_panic(errSpriteColKey, SDL_GetError());
2630 }
2631
2632
2633 /* Draw the sprite. */
2634 col = SDL_MapRGB(sprite->format,
2635 PaletteRGB[colix].r,
2636 PaletteRGB[colix].g,
2637 PaletteRGB[colix].b);
2638 fill_box(sprite, 0, 0, cellsize, cellsize, key);
2639 if (boxes & TL) {
2640 fill_box(sprite, 0, 0, bs, bs, col);
2641 }
2642 if (boxes & BL) {
2643 fill_box(sprite, 0, bs+ibsp, bs, bs, col);
2644 }
2645 if (boxes & TR) {
2646 fill_box(sprite, bs+ibsp, 0, bs, bs, col);
2647 }
2648 if (boxes & BR) {
2649 fill_box(sprite, bs+ibsp, bs+ibsp, bs, bs, col);
2650 }
2651
2652 /* Convert sprite to video format. */
2653 temp = SDL_DisplayFormat(sprite);
2654 SDL_FreeSurface(sprite);
2655 if (temp == NULL) {
2656 exit_panic(errSpriteConv, SDL_GetError());
2657 }
2658 sprite = temp;
2659
2660 /* Save it for later! */
2661 a->blitboxes[colix][boxes] = sprite;
2662 }
2663
2664 /* Predraw the blitboxes for a given colour. */
2665 static void
make_blitboxes_for_colix(Arena * a,int colix)2666 make_blitboxes_for_colix(Arena *a, int colix)
2667 {
2668 int i;
2669
2670 /* Allocate the blitboxes[] array? */
2671 if (a->blitboxes == NULL) {
2672 a->blitboxes = (SDL_Surface***)MALLOC(sizeof(SDL_Surface**)*NCOLOURS);
2673 if (a->blitboxes==NULL) {
2674 exit_panic(outOfMemory, NULL);
2675 }
2676 for (i=0; i<NCOLOURS; i++) {
2677 a->blitboxes[i] = NULL;
2678 }
2679 }
2680
2681 /* Make the boxes for this colour index? */
2682 if (a->blitboxes[colix] == NULL) {
2683 a->blitboxes[colix] = (SDL_Surface**)MALLOC(sizeof(SDL_Surface*)*16);
2684 if (a->blitboxes[colix] == NULL) {
2685 exit_panic(outOfMemory, NULL);
2686 }
2687 for (i=0; i<16; i++) {
2688 make_blitbox(a, colix, i);
2689 }
2690 }
2691 }
2692
2693
2694 /* Close the core panel. */
2695 static void
doclose_arena(void * parms)2696 doclose_arena(void *parms)
2697 {
2698 Arena *a = (Arena*)parms;
2699 free_blitboxes(a);
2700 if (a->core) {
2701 FREE((void*)a->core);
2702 a->core = NULL;
2703 }
2704 }
2705
2706 /* Layout doremode() callback initiates the predraw of BlitBoxes used for fast
2707 * blitting of core cells. */
2708 static void
doremode_arena(void * parms)2709 doremode_arena(void *parms)
2710 {
2711 Arena *a = (Arena*)parms;
2712 int warix;
2713 free_blitboxes(a);
2714
2715 for (warix=0; warix<warriors; warix++) {
2716 make_blitboxes_for_colix(a, WarColourIx[warix]);
2717 make_blitboxes_for_colix(a, DieColourIx[warix]);
2718 }
2719 BlitBoxes = a->blitboxes;
2720 }
2721
2722 static CellInfo
choose_cell(int mode)2723 choose_cell(int mode)
2724 {
2725 mode = min(9, mode);
2726 mode = max(0, mode);
2727 return DesiredCells[mode];
2728 }
2729
2730
2731 /* Initialise the Layout LAYOUT to be the CoreArena for a core of size
2732 * CORESIZE. */
2733 static void
init_arena(Layout * layout,size_t coresize,int displayMode)2734 init_arena(Layout *layout, size_t coresize, int displayMode)
2735 {
2736 Arena *a = &CoreArena;
2737
2738 a->coresize = coresize;
2739 a->core = (unsigned char*)MALLOC(sizeof(char)*4*coresize);
2740 if (!a->core) { exit_panic(outOfMemory, NULL); }
2741 memset((void*)a->core, 0, sizeof(unsigned char)*4*coresize);
2742
2743 a->cell = choose_cell(displayMode);
2744
2745 a->layout = layout;
2746 layout->parms = a;
2747 layout->before_layout = before_layout_arena;
2748 layout->after_layout = after_layout_arena;
2749 layout->doredraw = doredraw_arena;
2750 layout->doclear = doclear_arena;
2751 layout->doclose = doclose_arena;
2752 layout->doremode = doremode_arena;
2753 }
2754
2755 /* ------------------------------------------------------------------------
2756 * Status line.
2757 */
2758
2759 /* Layout doredraw() callback that draws the status line. Depends on
2760 * global inCdb. */
2761 static int
doredraw_status(Panel * p,void * parms)2762 doredraw_status(Panel *p, void *parms)
2763 {
2764 TextOutput *t = (TextOutput *)parms;
2765 const SDL_Rect *r = panel_view(p);
2766 int white = WHITE;
2767 int red = RED;
2768 int yellow = YELLOW;
2769 int i;
2770
2771 clear_panel(p);
2772 doclear_textoutput(p, parms);
2773
2774 /* Speed level meter. */
2775 type_nowrap(t, "<", white);
2776 for (i=0; i<SPEEDLEVELS - displaySpeed; i++) {
2777 if (t->x+CurrentFont.w+1 <= r->w) {
2778 outblankxy(r->x+t->x, r->y, red, r);
2779 t->x += CurrentFont.w+1;
2780 }
2781 }
2782 for (i=0; i<displaySpeed; i++) {
2783 if (t->x+CurrentFont.w <= r->w) {
2784 outblankxy(r->x+t->x+1, r->y, yellow, NULL);
2785 t->x += CurrentFont.w+1;
2786 }
2787 }
2788 type_nowrap(t, ">", white);
2789
2790 /* Detail level. */
2791 type_nowrap(t, " ", white);
2792 for (i=0; i<5; i++) {
2793 Uint32 col = i!=displayLevel ? white : red;
2794 char s[10];
2795 sprintf(s,"%d ", i);
2796 type_nowrap(t, s, col);
2797 }
2798
2799 /* Status */
2800 type_nowrap(t, " ", white);
2801 if (inCdb) {
2802 type_nowrap(t, "Debug", red);
2803 } else {
2804 type_nowrap(t, "Debug space Quit", white);
2805 }
2806 return 1;
2807 }
2808
2809 /* ------------------------------------------------------------------------
2810 * Warrior names and meters.
2811 */
2812
2813 /* Layout doredraw() callback types the names of the warriors into the warrior
2814 * names layout. */
2815 static int
doredraw_names(Panel * p,void * parms)2816 doredraw_names(Panel *p, void *parms)
2817 {
2818 TextOutput *t = (TextOutput*)parms;
2819 const SDL_Rect *r = panel_view(p);
2820 int i;
2821
2822 doclear_textoutput(p, parms);
2823 for (i=0; i<warriors; i++) {
2824 t->y = 0; /* space names evenly in panel. */
2825 t->x = i*r->w/warriors;
2826 type_nowrap(t, warrior[i].name, WarColourIx[i]);
2827 }
2828 return 1;
2829 }
2830
2831 /* Redraw a meter that is at offset YOFS in the rectangle R, has a count of
2832 * COUNT, a maximum count of MAXCOUNT, and a pixels per count ratio of
2833 * RATIO. */
2834 static void
redraw_meter(const SDL_Rect * r,int yofs,int count,int maxcount,int ratio,Uint32 col)2835 redraw_meter(const SDL_Rect *r, int yofs, int count, int maxcount, int ratio,
2836 Uint32 col)
2837 {
2838 SDL_Rect q;
2839
2840 Slock(TheSurf);
2841 putpixel(TheSurf, r->x + 1 + maxcount/ratio, r->y + yofs, col);
2842 Sunlock(TheSurf);
2843
2844 q.x = r->x; q.y = r->y + yofs;
2845 q.h = 1; q.w = 1 + count/ratio;
2846 SDL_FillRect(TheSurf, &q, col);
2847 }
2848
2849 /* Layout doredraw() callback redraws the cycle and process meters. */
2850 static int
doredraw_meters(Panel * p,void * parms)2851 doredraw_meters(Panel *p, void *parms)
2852 {
2853 const SDL_Rect *r = panel_view(p);
2854 int i;
2855
2856 parms = parms;
2857 clear_panel(p);
2858 for (i=0; i<warriors; i++) {
2859 Uint32 col = Colours[WarColourIx[i]];
2860 redraw_meter(r, splY[i]-r->y, warrior[i].tasks, taskNum,
2861 processRatio, col);
2862 }
2863
2864 redraw_meter(r, cycleY-r->y, cycle, warriors*cycles,
2865 cycleRatio, Colours[WHITE]);
2866 return 1;
2867 }
2868
2869 /* Layout after_layout() callback sets Y-coordinates and ratios of the various
2870 * meters. */
2871 static int
after_layout_meters(Panel * p,void * parms)2872 after_layout_meters(Panel *p, void *parms)
2873 {
2874 const SDL_Rect *r = panel_view(p);
2875 int i;
2876 parms = parms;
2877 for (i=0; i<warriors; i++) {
2878 splY[i] = r->y + 2*i;
2879 }
2880 processRatio = (taskNum + r->w-2)/(r->w-1);
2881 processRatio = max(1, processRatio);
2882
2883 MetersX = r->x;
2884 cycleY = r->y + 2*warriors+2;
2885 cycleRatio = (2*warriors*cycles + r->w-2)/(r->w-1);
2886 cycleRatio = max(1, cycleRatio);
2887 return 1;
2888 }
2889
2890 /* ------------------------------------------------------------------------
2891 * Resize/Redraw
2892 */
2893
2894 /* Redraw everything from scratch and update the display. */
2895 static void
redraw(void)2896 redraw(void)
2897 {
2898 Layout *root = get_root_layout(CoreLayout);
2899 redraw_layout(root);
2900 SDL_Flip(TheSurf);
2901 }
2902
2903 /* Layout everything into a display of size W by H pixels, enlarging the
2904 * display if necessary. This is the only function that calls set_VMode() to
2905 * open/resize the display, so it also initiates any one-time display-format
2906 * specific processing. */
2907 static void
relayout(Uint16 w,Uint16 h)2908 relayout(Uint16 w, Uint16 h)
2909 {
2910 #define BW 3
2911 #define BH 1
2912 Sint16 oldw = TheSurf ? TheVMode.w : -1;
2913 Sint16 oldh = TheSurf ? TheVMode.h : -1;
2914 Layout *root = get_root_layout(CoreLayout);
2915 CoreLayout->maxh = UNBOUNDED;
2916 CoreArena.desired_cell = choose_cell(displayMode);
2917
2918 w = max(2*BW, w);
2919 h = max(2*BH, h);
2920
2921 /* Attempt to compute a suitable layout. */
2922 if (!layout(root, BW, BH, w-2*BW, h-2*BH)) {
2923 /* Failed with the desired width/height. Try again by adjusting for
2924 * the minimum bounds. */
2925 if (w <= root->minw+2*BW) { w = root->minw + 2*BW; }
2926 if (h <= root->minh+2*BH) { h = root->minh + 2*BH; }
2927
2928 if (!layout(root, BW, BH, w-2*BW, h-2*BH)) {
2929 if (w <= root->minw+2*BW) { w = root->minw + 2*BW; }
2930 if (h <= root->minh+2*BH) { h = root->minh + 2*BH; }
2931 if (!layout(root, BW, BH, w-2*BW, h-2*BH)) {
2932 /* Still no layout. Bail. */
2933 exit_panic("Couldn't compute a suitable layout", "BUG");
2934 }
2935 }
2936 }
2937
2938 if (!TheSurf || oldw != w || oldh != h) {
2939 if (TheSurf) {
2940 SDL_FreeSurface(TheSurf);
2941 TheSurf = NULL;
2942 }
2943 TheVMode.w = w;
2944 TheVMode.h = h;
2945 TheSurf = set_VMode(&TheVMode);
2946 if (!TheSurf) {
2947 exit_panic(errDisplayOpen, SDL_GetError());
2948 }
2949 }
2950 #if 0
2951 printf("relayout ok\n");
2952 #endif
2953 remode_layout(root);
2954 redraw();
2955 }
2956
2957 /* Initialise the global layout structures. The display does not need to be
2958 * open. */
2959 static void
init_layout(void)2960 init_layout(void)
2961 {
2962 int i;
2963 Layout *names = NULL;
2964
2965 /* Open arena layout. */
2966 CoreLayout = new_layout(200, UNBOUNDED,
2967 0, UNBOUNDED, border(2,2,2,2,WHITE));
2968 init_arena(CoreLayout, coreSize, displayMode);
2969
2970 /* Open meters panel. */
2971 MetersLayout =
2972 split_layout(CoreLayout, ABOVES, 0, 2*warriors+3, 2*warriors+3,
2973 border(-2,-2,-2,-2,WHITE));
2974 MetersLayout->minw = 2;
2975 MetersLayout->doredraw = doredraw_meters;
2976 MetersLayout->doclear = doredraw_meters;
2977 MetersLayout->after_layout = after_layout_meters;
2978
2979 /* Open names panel. */
2980 if (warriors <= 2) {
2981 names = split_layout(MetersLayout, ABOVES, 0,
2982 CurrentFont.h, CurrentFont.h,
2983 noborder);
2984 names->parms = alloc_text_output(names);
2985 names->doredraw = names->doclear = doredraw_names;
2986 }
2987
2988 /* Open the status line panel. */
2989 StatusLayout = split_layout(CoreLayout, BELOWS, 0,
2990 CurrentFont.h, CurrentFont.h, noborder);
2991 StatusLayout->parms = alloc_text_output(StatusLayout);
2992 StatusLayout->doredraw = doredraw_status;
2993 StatusLayout->doclear = doredraw_status;
2994
2995 /* Initialise input panels. We open an input panel that takes 0% of
2996 * the space of the core. The layout algorithm will see to it that
2997 * whatever space is left over from the core will be given to the text
2998 * panels.
2999 */
3000 for (i=0; i<MAXTEXTINPUTS; i++) { TextPanels[i] = NULL; }
3001 TextPanels[0] =
3002 alloc_text_input(
3003 split_layout(StatusLayout, BELOWS, 0, 2*CurrentFont.h, UNBOUNDED,
3004 noborder));
3005 set_prompt(TextPanels[0], CDB_PROMPT);
3006 give_focus(TextPanels[0]);
3007 NTextPanels = 1;
3008 curPanel = 1;
3009
3010 /* Set warrior colours. */
3011 if (warriors <= 2) {
3012 WarColourIx[0] = GREEN;
3013 DieColourIx[0] = WarColourIx[0] % (NCOLOURS-1) + 1;
3014 WarColourIx[1] = LIGHTRED;
3015 DieColourIx[1] = WarColourIx[1] % (NCOLOURS-1) + 1;
3016 } else {
3017 int i;
3018 for (i=0; i<MAXWARRIOR; i++) {
3019 WarColourIx[i] = DieColourIx[i] = ((9-1+i) % (NCOLOURS-1)) + 1;
3020 }
3021 }
3022 }
3023
3024 /* ------------------------------------------------------------------------
3025 * Layout interface
3026 */
3027
3028 /* Clear the core arena. */
3029 void
sdlgr_clear_arena(void)3030 sdlgr_clear_arena(void)
3031 {
3032 clear_layout(CoreLayout);
3033 refresh_layout(CoreLayout);
3034 }
3035
3036 /* Clear the arena and redraw the meters. */
3037 void
sdlgr_display_clear(void)3038 sdlgr_display_clear(void)
3039 {
3040
3041 clear_layout(CoreLayout);
3042 redraw_layout(MetersLayout);
3043 refresh_layout(CoreLayout);
3044 refresh_layout(MetersLayout);
3045 }
3046
3047 /* Relayout everything. */
3048 void
sdlgr_relayout(void)3049 sdlgr_relayout(void)
3050 {
3051 relayout(TheVMode.w, TheVMode.h);
3052 }
3053
3054 /* Refresh things on the screen. */
3055 void
sdlgr_refresh(int what)3056 sdlgr_refresh(int what)
3057 {
3058 switch (what) {
3059 case -1:
3060 refresh_layout(get_root_layout(CoreLayout));
3061 case 0:
3062 refresh_layout(CoreLayout);
3063 break;
3064 default: {
3065 int i;
3066 for (i=0; i<NTextPanels; i++) {
3067 refresh_layout(TextPanels[i]->layout);
3068 }
3069 break;
3070 }
3071 }
3072 }
3073
3074 /* Redraw the status line. */
3075 void
sdlgr_write_menu(void)3076 sdlgr_write_menu(void)
3077 {
3078 redraw_layout(StatusLayout);
3079 refresh_layout(StatusLayout);
3080 }
3081
3082 /* ------------------------------------------------------------------------
3083 * Text Panels
3084 */
3085
3086 /* Create new text panels by splitting an old one. */
3087 static int
split_text_panel(int panel,int size,Uint16 method)3088 split_text_panel(int panel, int size, Uint16 method)
3089 {
3090 assert(1 <= panel && panel <= NTextPanels);
3091 assert(TextPanels[panel-1]);
3092 size = min(100, size);
3093 size = max(0, size);
3094
3095 if (NTextPanels < MAXTEXTINPUTS) {
3096 Layout *a = TextPanels[panel-1]->layout;
3097 Layout *b;
3098 TextInput *t;
3099 SDL_Rect r = a->p.r;
3100 int newpanel;
3101 b = split_layout(a,
3102 method, size,
3103 0, UNBOUNDED,
3104 noborder);
3105 b->minh = CurrentFont.h;
3106 b->minw = CurrentFont.w;
3107
3108 TextPanels[NTextPanels] = t = alloc_text_input(b);
3109 set_prompt(t, CDB_PROMPT);
3110 newpanel = ++NTextPanels;
3111
3112 /* Attempt to re-layout only the pair layout that contains the
3113 * new text panel. If it fails, then do a full re-layout.
3114 * This avoids having to redraw the arena on an update that
3115 * doesn't affect the core panel. */
3116 if (layout(a->parent, r.x, r.y, r.w, r.h)) {
3117 redraw_layout(b);
3118 refresh_layout(b);
3119 } else {
3120 relayout(TheVMode.w, TheVMode.h);
3121 }
3122 return newpanel;
3123 }
3124 return -1;
3125 }
3126
3127 /* Close a text panel. */
3128 static void
close_text_panel(int panel)3129 close_text_panel(int panel)
3130 {
3131 int i;
3132 SDL_Rect r;
3133 int need_redraw = 0;
3134 Layout *a, *b;
3135
3136 if (panel < 1 || panel > NTextPanels) { return; }
3137 if (TextPanels[panel-1] == NULL) { return; }
3138 if (NTextPanels <= 1) { return; }
3139
3140 a = TextPanels[panel-1]->layout;
3141 b = get_sibling_layout(a);
3142 r = a->parent->p.r;
3143 need_redraw = r.x != b->p.r.x || r.y != b->p.r.y;
3144 clear_layout(a);
3145 refresh_layout(a);
3146 close_layout(a);
3147 TextPanels[panel-1] = NULL;
3148
3149 /* Shift the remaining panels down. */
3150 for (i=panel; i<NTextPanels; i++) {
3151 TextPanels[i-1] = TextPanels[i];
3152 }
3153 --NTextPanels;
3154 TextPanels[NTextPanels] = NULL;
3155
3156 if (layout(b, r.x, r.y, r.w, r.h)) {
3157 if (need_redraw) {
3158 TextInput *t = (TextInput*)(b->parms);
3159 t->needs_prompting = 1;
3160 clear_layout(b);
3161 refresh_layout(b);
3162 }
3163 } else {
3164 relayout(TheVMode.w, TheVMode.h);
3165 }
3166 }
3167
3168 /* Update the cdb text panels. If NEXTPANEL == 0 then the current panel is
3169 * closed. Otherwise, set focus to the panel NEXTPANEL (creating a new one if
3170 * NEWPANEL doesn't already exist). The global curPanel is updated to reflect
3171 * the new panel. NOTE: curPanel may be != NEXTPANEL if a new panel had to be
3172 * created, in which case curPanel is the id of the new panel. */
3173 void
sdlgr_update(int nextpanel)3174 sdlgr_update(int nextpanel)
3175 {
3176 /* Initialise? */
3177 if (curPanel <= 0) {
3178 curPanel = 1;
3179 assert(TextPanels[0]!=NULL);
3180 }
3181
3182 /* Is this a close request? */
3183 if (nextpanel == 0) {
3184 close_text_panel(curPanel);
3185 nextpanel = curPanel <= NTextPanels ? curPanel : NTextPanels;
3186 } else {
3187 take_focus(TextPanels[curPanel-1]);
3188 /* Do we need to create a new panel? */
3189 if (TextPanels[nextpanel-1] == NULL) {
3190 nextpanel = split_text_panel(NTextPanels, 50, RIGHTS);
3191 if (nextpanel<=0) { /* split failed? */
3192 return; /* yup, give up. */
3193 }
3194 }
3195 }
3196 curPanel = nextpanel;
3197 give_focus(TextPanels[curPanel-1]);
3198 }
3199
3200 /* Clear the current panel. */
3201 void
sdlgr_clear(void)3202 sdlgr_clear(void)
3203 {
3204 Layout *a = TextPanels[curPanel-1]->layout;
3205 clear_layout(a);
3206 }
3207
3208 /* Type a string to the current panel. */
3209 void
sdlgr_puts(const char * s)3210 sdlgr_puts(const char *s)
3211 {
3212 TextOutput *t = TextPanels[curPanel-1]->out;
3213 int colix = TextPanels[curPanel-1]->colix;
3214 if (printAttr) {
3215 colix = WarColourIx[printAttr-1];
3216 }
3217 type(t, s, colix);
3218 }
3219
3220 /* Get the number of rows in the current panel. */
3221 int
sdlgr_text_lines(void)3222 sdlgr_text_lines(void)
3223 {
3224 int rows;
3225 Panel *p = &TextPanels[curPanel-1]->layout->p;
3226 panel_text_size(p, NULL, &rows);
3227 return rows;
3228 }
3229
3230
3231 /* ------------------------------------------------------------------------
3232 * History ring
3233 *
3234 * This section provides command line history facilities for the event
3235 * handlers to use.
3236 **/
3237
3238 static Cmd *
get_cmd_ring()3239 get_cmd_ring()
3240 {
3241 static int done_init = 0;
3242 if (!done_init) {
3243 CmdRing.cmd = xstrdup("");
3244 CmdRing.prev = CmdRing.next = &CmdRing;
3245 done_init = 1;
3246 }
3247 return &CmdRing;
3248 }
3249
3250 static void
add_cmd(const char * cmd)3251 add_cmd(const char *cmd)
3252 {
3253 Cmd *ring = get_cmd_ring();
3254 Cmd *c = MALLOC(sizeof(Cmd));
3255 if (c == NULL) {
3256 exit_panic(outOfMemory, NULL);
3257 }
3258 c->cmd = xstrdup(cmd);
3259 { char *s = c->cmd; while (s && *s) { if (*s=='\n') { *s='\0'; } s++; } }
3260 c->next = ring->next;
3261 c->prev = ring;
3262 ring->next->prev = c;
3263 ring->next = c;
3264 }
3265
3266
3267 /* ------------------------------------------------------------------------
3268 * Keyboard and mouse input; event handlers.
3269 */
3270 #define any_set(bits,mask) (((bits)&(mask))!=0)
3271
3272 /* Handle VIDEORESIZE, VIDEOEXPOSE, and QUIT events. */
3273 static void
default_handler(SDL_Event * e)3274 default_handler(SDL_Event *e)
3275 {
3276 #ifdef WIN32
3277 /* Under windows we ignore the very first VIDEOEXPOSE event. */
3278 static int nvideoexpose_events = 0;
3279 #endif
3280 switch (e->type) {
3281 case SDL_VIDEORESIZE:
3282 relayout(e->resize.w, e->resize.h);
3283 SDL_Flip(TheSurf);
3284 break;
3285 case SDL_VIDEOEXPOSE:
3286 #ifdef WIN32
3287 if (++nvideoexpose_events > 1) {
3288 redraw();
3289 }
3290 #endif
3291 break;
3292 case SDL_QUIT:
3293 sdlgr_display_close(WAIT);
3294 Exit(USERABORT);
3295 }
3296 }
3297
3298 /* Handle special key strokes: CTRL-C, CTRL-BREAK, ALT-RETURN.
3299 * Returns 1 if the key is handled, and 0 otherwise. */
3300 static int
special_keyhandler(const SDL_keysym * s)3301 special_keyhandler(const SDL_keysym *s)
3302 {
3303 /* Process CTRL-C, CTRL-BREAK */
3304 if (any_set(s->mod, KMOD_CTRL)
3305 && (s->sym == SDLK_c
3306 || s->sym==SDLK_BREAK))
3307 {
3308 SDL_Event e;
3309 e.type = SDL_QUIT;
3310 default_handler(&e); /* shouldn't return. */
3311 }
3312
3313 /* Toggle fullscreen mode on ALT-RETURN */
3314 if ((any_set(s->mod, KMOD_ALT)
3315 || any_set(s->mod, KMOD_MODE))
3316 && (s->sym == SDLK_RETURN))
3317 {
3318 SDL_WM_ToggleFullScreen(TheSurf);
3319 return 1;
3320 }
3321 return 0;
3322 }
3323
3324
3325 /** Deal with keypad and other conversion weirdness. */
3326 static void
normalise_keysym(SDL_keysym * s)3327 normalise_keysym(SDL_keysym *s)
3328 {
3329 int num = any_set(s->mod, KMOD_NUM);
3330
3331 /* Windows (or perhaps SDL) doesn't handle keystrokes with AltGr too well.
3332 * Problems include: spurious modifier bits, iffy unicode conversion for
3333 * valid international keys, varying keysyms on different versions of windows,
3334 * and so on and so forth. Ignoring all keys modified by AltGr doesn't work
3335 * well because some characters (e.g. @ $) can only be typed using AltGr
3336 * on scandinavian keyboards.
3337 */
3338 if (any_set(s->mod, KMOD_RALT)) {
3339 s->mod = KMOD_RALT;
3340 }
3341
3342 /* Keypad */
3343 switch (s->sym) {
3344 case SDLK_KP0:
3345 if (num) { s->sym = s->unicode = '0'; } else { s->sym = SDLK_INSERT; }
3346 break;
3347 case SDLK_KP1:
3348 if (num) { s->sym = s->unicode = '1'; } else { s->sym = SDLK_END; }
3349 break;
3350 case SDLK_KP2:
3351 if (num) { s->sym = s->unicode = '2'; } else { s->sym = SDLK_DOWN; }
3352 break;
3353 case SDLK_KP3:
3354 if (num) { s->sym = s->unicode = '3'; } else { s->sym = SDLK_PAGEDOWN;}
3355 break;
3356 case SDLK_KP4:
3357 if (num) { s->sym = s->unicode = '4'; } else { s->sym = SDLK_LEFT; }
3358 break;
3359 case SDLK_KP5:
3360 if (num) { s->sym = s->unicode = '5'; }
3361 break;
3362 case SDLK_KP6:
3363 if (num) { s->sym = s->unicode = '6'; } else { s->sym = SDLK_RIGHT; }
3364 break;
3365 case SDLK_KP7:
3366 if (num) { s->sym = s->unicode = '7'; } else { s->sym = SDLK_HOME; }
3367 break;
3368 case SDLK_KP8:
3369 if (num) { s->sym = s->unicode = '8'; } else { s->sym = SDLK_UP; }
3370 break;
3371 case SDLK_KP9:
3372 if (num) { s->sym = s->unicode = '9'; } else { s->sym = SDLK_PAGEUP; }
3373 break;
3374 }
3375
3376 }
3377
3378 /* Convert a key from a SDL_keysym structure to ASCII. Returns 0 if
3379 * the key isn't an ASCII character, and the character otherwise. */
3380 static char
conv_key(const SDL_keysym * s)3381 conv_key(const SDL_keysym *s)
3382 {
3383 if (s->unicode >= 128) { return 0; } /* non-ASCII */
3384 if (any_set(s->mod, KMOD_CTRL | KMOD_LALT | KMOD_META)) {
3385 return 0; /* special, modified */
3386 }
3387 switch (s->sym) {
3388 case SDLK_RETURN:
3389 case SDLK_KP_ENTER:
3390 return '\n';
3391 case SDLK_TAB:
3392 return '\t';
3393 case SDLK_DELETE:
3394 case SDLK_BACKSPACE:
3395 return '\b';
3396 case SDLK_ESCAPE:
3397 return 27; /* ASCII for escape. */
3398 default:
3399 return s->unicode;
3400 }
3401 return 0;
3402 }
3403
3404 static void
print_keysym(const SDL_keysym * s)3405 print_keysym(const SDL_keysym *s)
3406 {
3407 fprintf(stderr,"sym: %d, unicode: %d '%c', mod: %x\n",
3408 s->sym, s->unicode,
3409 isgraph(s->unicode) ? s->unicode : '.',
3410 s->mod);
3411 }
3412
3413
3414 /* Do special processing for macro keys: if the key is a modified
3415 * key or non-ASCII key (function key, arrow keys, etc.) return the
3416 * name of the key in the form " m <modifiers><keyname>\n" in the
3417 * buffer BUF. Returns 1 if an expansion was made, and 0 if not. */
3418 static int
macro_key(const SDL_keysym * s,char * buf,int maxbuf)3419 macro_key(const SDL_keysym *s, char *buf, int maxbuf)
3420 {
3421 char name[MAXALLCHAR];
3422 int ok = 0;
3423 int modified = 0;
3424 static const size_t n = MAXALLCHAR;
3425 if (s->sym == SDLK_UNKNOWN) { return 0; }
3426
3427 xstrncpy(name, " m ", n);
3428 if (any_set(s->mod, KMOD_CTRL)) {
3429 xstrncat(name, "ctrl-", n); modified = 1;
3430 }
3431 if (any_set(s->mod, KMOD_LALT)) {
3432 xstrncat(name, "alt-", n); modified = 1;
3433 }
3434 if (any_set(s->mod, KMOD_META)) {
3435 xstrncat(name, "meta-", n); modified = 1;
3436 }
3437
3438 switch (s->sym) {
3439 case SDLK_UP:
3440 xstrncat(name, "up", n); ok=1;
3441 break;
3442 case SDLK_DOWN:
3443 xstrncat(name, "down", n); ok=1;
3444 break;
3445 case SDLK_RIGHT:
3446 xstrncat(name, "right", n); ok=1;
3447 break;
3448 case SDLK_LEFT:
3449 xstrncat(name, "left", n); ok=1;
3450 break;
3451 case SDLK_INSERT:
3452 xstrncat(name, "ins", n); ok=1;
3453 break;
3454 case SDLK_HOME:
3455 xstrncat(name, "home", n); ok=1;
3456 break;
3457 case SDLK_END:
3458 xstrncat(name, "end", n); ok=1;
3459 break;
3460 case SDLK_PAGEUP:
3461 xstrncat(name, "pgup", n); ok=1;
3462 break;
3463 case SDLK_PAGEDOWN:
3464 xstrncat(name, "pgdn", n); ok=1;
3465 break;
3466 case SDLK_F1: case SDLK_F2: case SDLK_F3: case SDLK_F4:
3467 case SDLK_F5: case SDLK_F6: case SDLK_F7: case SDLK_F8:
3468 case SDLK_F9: case SDLK_F10: case SDLK_F11: case SDLK_F12:
3469 case SDLK_F13: case SDLK_F14: case SDLK_F15:
3470 {
3471 char keyname[5];
3472 sprintf(keyname, "f%d", 1+s->sym-SDLK_F1);
3473 xstrncat(name, keyname, n); ok=1;
3474 break;
3475 }
3476 case SDLK_HELP:
3477 xstrncat(name, "help", n); ok=1;
3478 break;
3479
3480 default:
3481 if (modified) {
3482 if (isgraph(s->unicode)) {
3483 xstrnapp(name, s->unicode, n); ok=1;
3484 }
3485 else if (s->sym >= 33 && s->sym < 128) {
3486 xstrnapp(name, s->sym, n); ok=1;
3487 }
3488 }
3489 }
3490 if (ok) {
3491 xstrncat(name, "\n", n);
3492 xstrncpy(buf, name, maxbuf);
3493 }
3494 return ok;
3495 }
3496
3497 /* Get a string from a cdb panel and do special input processing. */
3498 char *
sdlgr_gets(char * buf,int maxbuf,const char * prompt)3499 sdlgr_gets(char *buf, int maxbuf, const char *prompt)
3500 {
3501 SDL_Event ev;
3502 SDL_Event *e = &ev;
3503 int done = 0; /* got input? */
3504 int hide = 0; /* if true, user input is overriden
3505 by a key/mouse macro */
3506 Cmd *curcmd = get_cmd_ring();
3507 int i;
3508
3509 if (inputRedirection) {
3510 return fgets(buf, maxbuf, stdin);
3511 }
3512
3513 /* Set prompt for current panel. */
3514 assert(prompt);
3515 set_prompt(TextPanels[curPanel-1], prompt);
3516
3517 /* Reactivate text panels. */
3518 for (i=0; i<NTextPanels; i++) {
3519 activate(TextPanels[i]);
3520 refresh_layout(TextPanels[i]->layout);
3521 }
3522
3523 SDL_WarpMouse(xkoord(curAddr), ykoord(curAddr));
3524
3525 /* Process events until we have input to pass back. */
3526 while (!done) {
3527 TextInput *t;
3528 while (0==SDL_WaitEvent(e)) {} /* Get an event, ignore errors. */
3529 t = TextPanels[curPanel-1];
3530
3531 switch (e->type) {
3532 case SDL_MOUSEBUTTONDOWN:
3533 /* Expand mouse button macros. */
3534 switch (e->button.button) {
3535 case 1: xstrncpy(buf, " m mousel\n", maxbuf); done=hide=1; break;
3536 case 2: xstrncpy(buf, " m mousem\n", maxbuf); done=hide=1; break;
3537 case 3: xstrncpy(buf, " m mouser\n", maxbuf); done=hide=1; break;
3538 }
3539 /* Accept mouse input and update curAddr only if the click
3540 * is inside core. */
3541 if (done) {
3542 int x = e->button.x;
3543 int y = e->button.y;
3544 if (x >= ArenaX && y >= ArenaY)
3545 {
3546 int col = (x-ArenaX)/CellSize;
3547 int row = (y-ArenaY)/CellSize;
3548 int addr = col + row*CellsPerLine;
3549 if (col < CellsPerLine && addr < (int)coreSize) {
3550 curAddr = addr;
3551 } else {
3552 xstrncpy(buf, "", maxbuf);
3553 done = hide = 0;
3554 }
3555 }
3556 }
3557 break;
3558
3559 case SDL_KEYDOWN:
3560 /* Handle CTRL-C, CTRL-BREAK, ALT-RETURN, etc. */
3561 if (special_keyhandler(&e->key.keysym)) { break; }
3562
3563 /* Possibly a history key? */
3564 if (any_set(e->key.keysym.mod, KMOD_SHIFT)) {
3565 if (e->key.keysym.sym == SDLK_UP) { /* SHIFT-UP */
3566 curcmd = curcmd->next;
3567 reset_input(t, curcmd->cmd);
3568 refresh_layout(t->layout);
3569 continue; /* processing events. */
3570 }
3571 if (e->key.keysym.sym == SDLK_DOWN) { /* SHIFT-DOWN */
3572 curcmd = curcmd->prev;
3573 reset_input(t, curcmd->cmd);
3574 refresh_layout(t->layout);
3575 continue; /* processing events. */
3576 }
3577 }
3578
3579 /* print_keysym(&e->key.keysym); */
3580 normalise_keysym(&e->key.keysym);
3581 /* Expand macro keys? (F1, F2, ALT-K, etc.) */
3582 if (macro_key(&e->key.keysym, buf, maxbuf)) {
3583 done = hide = 1;
3584 }
3585 else {
3586 int c;
3587 /* Key goes into the current text input panel. */
3588 c = conv_key(&e->key.keysym);
3589 switch (c) {
3590 case 0: /* Unknown key, ignore. */
3591 break;
3592 case '\b': /* DEL, BACKSPACE erases a character.*/
3593 del_char(t);
3594 refresh_layout(t->layout);
3595 break;
3596 case '\t': /* TAB moves focus to next panel. */
3597 take_focus(t);
3598 refresh_layout(t->layout);
3599 curPanel = 1 + (curPanel-1 + 1) % NTextPanels;
3600 t = TextPanels[curPanel-1];
3601 give_focus(t);
3602 refresh_layout(t->layout);
3603 break;
3604 case '\n': /* RETURN, ENTER finishes input. */
3605 add_char(t, c);
3606 finish_input(t, buf, maxbuf);
3607 done = 1;
3608 break;
3609 default: /* It's an ASCII char; add it. */
3610 if (isgraph(c) || isspace(c)) {
3611 add_char(t, c);
3612 refresh_layout(t->layout);
3613 break;
3614 }
3615 }
3616 }
3617 break;
3618 /* The default handler handles window resize, quit, and
3619 expose events */
3620 default:
3621 default_handler(e);
3622 }
3623 }
3624
3625 /* If user input was overriden by a key/mouse macro, show the
3626 * expansion text as if the user had input it (but don't forget
3627 * what the user was composing.) Also, don't enter the expansion
3628 * text into the history ring. */
3629 if (hide) {
3630 TextInput *t = TextPanels[curPanel-1];
3631 t->needs_prompting = 1; /* Forces redraw of prompt and
3632 the interrupted input line
3633 next time the panel is activated.
3634 */
3635 hide_input(t);
3636 type(t->out, buf, t->colix);
3637 } else {
3638 /* Add user input to the history ring if it's not empty. */
3639 char *s = buf;
3640 while (s && isspace(*s)) { s++; }
3641 if (s && *s!=0) {
3642 add_cmd(buf);
3643 }
3644 }
3645
3646 /* Deactivate the text panels so they don't redraw the prompt when
3647 * they are resized/exposed. */
3648 for (i=0; i<NTextPanels; i++) {
3649 deactivate(TextPanels[i]);
3650 refresh_layout(TextPanels[i]->layout);
3651 }
3652
3653 return buf;
3654 }
3655
3656
3657 /* ------------------------------------------------------------------------
3658 * Core display interface
3659 */
3660
3661 void
sdlgr_set_displayMode(int newmode)3662 sdlgr_set_displayMode(int newmode)
3663 {
3664 newmode = min(9, newmode);
3665 newmode = max(0, newmode);
3666 if (newmode != displayMode) {
3667 displayMode = newmode;
3668 if (TheSurf) {
3669 sdlgr_relayout();
3670 }
3671 }
3672 }
3673
3674 void
sdlgr_set_displaySpeed(int newspeed)3675 sdlgr_set_displaySpeed(int newspeed)
3676 {
3677 newspeed = max(newspeed, 0);
3678 displaySpeed = min(newspeed, SPEEDLEVELS-1);
3679 keyDelay = keyDelayAr[displaySpeed];
3680 keyDelay = min(keyDelay, cycles>1 ? cycles-1 : 1);
3681 if (displayLevel == 0) {
3682 keyDelay = 10000;
3683 }
3684 }
3685
3686 void
sdlgr_set_displayLevel(int newlevel)3687 sdlgr_set_displayLevel(int newlevel)
3688 {
3689 newlevel = min(4, newlevel);
3690 displayLevel = max(newlevel, 0);
3691 sdlgr_set_displaySpeed(displaySpeed);
3692 }
3693
3694 static void
sdlgr_display_cycle()3695 sdlgr_display_cycle()
3696 {
3697 if (cycle % cycleRatio == 0) {
3698 Sint16 x = MetersX + cycle/cycleRatio;
3699 Slock(TheSurf);
3700 putpixel(TheSurf, x, cycleY, Colours[BLACK]);
3701 Sunlock(TheSurf);
3702 /*SDL_UpdateRect(TheSurf, x, cycleY, 1, 1);*/
3703 }
3704
3705 /* If it's time to check for keys and refresh the display, do so. */
3706 if (cycle % keyDelay == 0) {
3707 SDL_Event e;
3708 int key = 0;
3709
3710 /* Check for key presses and display changes. */
3711 if (SDL_PollEvent(&e)) {
3712 switch (e.type) {
3713 case SDL_KEYDOWN:
3714 if (!special_keyhandler(&e.key.keysym)) {
3715 key = conv_key(&e.key.keysym);
3716 }
3717 break;
3718 default:
3719 default_handler(&e);
3720 }
3721
3722 }
3723 if (key && !inputRedirection) {
3724 switch (key) {
3725 case ' ':
3726 case 'r':
3727 case 'R':
3728 sdlgr_clear_arena();
3729 break;
3730
3731 case '>':
3732 sdlgr_set_displaySpeed(displaySpeed-1);
3733 break;
3734
3735 case '<':
3736 sdlgr_set_displaySpeed(displaySpeed+1);
3737 break;
3738
3739 case 27: /* ASCII for escape. */
3740 case 'Q':
3741 case 'q':
3742 e.type = SDL_QUIT;
3743 default_handler(&e);
3744 break;
3745
3746 case '0': sdlgr_set_displayLevel(0); break;
3747 case '1': sdlgr_set_displayLevel(1); break;
3748 case '2': sdlgr_set_displayLevel(2); break;
3749 case '3': sdlgr_set_displayLevel(3); break;
3750 case '4': sdlgr_set_displayLevel(4); break;
3751
3752 default:
3753 sighandler(0); /* terminate macros in progress, enter
3754 debug state. */
3755 break;
3756 }
3757 sdlgr_write_menu();
3758 }
3759 refresh_layout(CoreLayout);
3760 refresh_layout(MetersLayout);
3761 }
3762 }
3763
3764 #define display_die(warnum)
3765 #define display_push(warnum)
3766 #define display_init() sdlgr_open_graphics()
3767 #define display_clear() sdlgr_display_clear()
3768 #define display_close() sdlgr_display_close(WAIT)
3769 #define display_cycle() sdlgr_display_cycle()
3770
3771 #define display_read(addr) \
3772 do {\
3773 if (displayLevel > 3)\
3774 display_box((addr), TL, WarColourIx[W-warrior]);\
3775 } while (0)
3776
3777 #define display_write(addr) \
3778 do {\
3779 if (displayLevel > 1)\
3780 display_box((addr), TR|BL, WarColourIx[W-warrior]);\
3781 } while (0)
3782
3783 #define display_dec(addr) \
3784 do {\
3785 if (displayLevel > 2)\
3786 display_box((addr), TL|TR, WarColourIx[W-warrior]);\
3787 } while (0)
3788
3789 #define display_inc(addr) \
3790 do {\
3791 if (displayLevel > 2)\
3792 display_box((addr), TL|BL, WarColourIx[W-warrior]);\
3793 } while (0)
3794
3795 #define display_exec(addr) \
3796 do {\
3797 if (displayLevel > 0)\
3798 display_box((addr), TL|TR|BL|BR, WarColourIx[W-warrior]);\
3799 } while (0)
3800
3801 #define display_spl(warNum, tasks) \
3802 do {\
3803 int __w = (warNum);\
3804 Sint16 x = MetersX + (tasks)/processRatio;\
3805 Sint16 y = splY[__w];\
3806 Slock(TheSurf);\
3807 putpixel(TheSurf, x, y, Colours[WarColourIx[__w]]);\
3808 Sunlock(TheSurf);\
3809 /* SDL_UpdateRect(TheSurf, x, y, 1, 1); */\
3810 } while (0)
3811
3812 #define display_dat(addr, warNum, tasks) \
3813 do {\
3814 int __w = (warNum);\
3815 int x = MetersX + (tasks)/processRatio;\
3816 int y = splY[__w];\
3817 if (displayLevel > 0)\
3818 display_box((addr), TL|TR|BL|BR, DieColourIx[__w]);\
3819 Slock(TheSurf);\
3820 putpixel(TheSurf, x, y, Colours[BLACK]);\
3821 Sunlock(TheSurf);\
3822 /* SDL_UpdateRect(TheSurf, x, y, 1, 1); */\
3823 } while (0)
3824
3825 /* ------------------------------------------------------------------------
3826 * Graphics display open/close.
3827 */
3828
3829 /* atexit() hook that quits SDL. */
3830 static void
exit_hook(void)3831 exit_hook(void)
3832 {
3833 static int has_quit = 0;
3834 if (has_quit) { return; }
3835
3836 has_quit = 1;
3837 if (TheSurf) {
3838 SDL_FreeSurface(TheSurf);
3839 TheSurf = NULL;
3840 }
3841 SDL_Quit();
3842 }
3843
3844 /* Called by pMARS to initialise the SDL graphics subsystem. */
3845 void
sdlgr_open_graphics(void)3846 sdlgr_open_graphics(void)
3847 {
3848 const char *modestr;
3849
3850 /* Initialise SDL. */
3851 atexit(exit_hook);
3852 if (SDL_Init(SDL_INIT_VIDEO)) {
3853 exit_panic(failedSDLInit, SDL_GetError());
3854 }
3855 TheSurf = NULL;
3856 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
3857 SDL_EnableUNICODE(1);
3858
3859 /* Decode -mode argument. */
3860 modestr = sdlgr_Storage[0] ? sdlgr_Storage[0] : "";
3861 if ((modestr = decode_vmode_string(&TheVMode, modestr))) {
3862 exit_panic(badModeString, modestr);
3863 }
3864
3865 /* Preinitialise the font. (= clear it out with NULLs.)*/
3866 Font_Init(&CurrentFont, NULL, 8, 16, 128);
3867
3868 /* Set up the layout. */
3869 init_layout();
3870 relayout(TheVMode.w, TheVMode.h);
3871 }
3872
3873 /* Called by pMARS to close the graphics subsystem. If WAIT is non-zero, then
3874 * prints a message waits for a keypress before shutting down. */
3875 void
sdlgr_display_close(int wait)3876 sdlgr_display_close(int wait)
3877 {
3878 SDL_Event e;
3879 if (wait == WAIT) {
3880 sdlgr_puts(pressAnyKey);
3881 sdlgr_refresh(curPanel);
3882 }
3883 while (wait == WAIT) {
3884 if (SDL_WaitEvent(&e)) {
3885 switch (e.type) {
3886 case SDL_KEYDOWN:
3887 wait = NOWAIT; break;
3888 default:
3889 default_handler(&e);
3890 }
3891 }
3892 }
3893 exit_hook();
3894 }
3895