1 /*
2  * sf_video.c - SDL interface, graphics functions
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or visit http://www.fsf.org/
20  */
21 
22 #include "sf_frotz.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <SDL.h>
29 
30 #include "generic.h"
31 
32 static char banner[256];
33 static int isfullscreen;
34 static ulong *sbuffer = NULL;
35 static int sbpitch;		/* in longs */
36 static int dirty = 0;
37 static int ewidth, eheight;
38 static SDL_Window *window = NULL;
39 static SDL_Renderer *renderer = NULL;
40 static SDL_Texture *texture = NULL;
41 int m_timerinterval = 100;
42 
43 extern bool sdl_active;
44 
45 static void sf_quitconf();
46 
47 static bool ApplyPalette(sf_picture *);
48 static ulong screen_palette[16];
49 
50 extern z_header_t z_header;
51 
52 /* clipping region */
53 static int xmin, xmax, ymin, ymax;
54 
sf_setclip(int x,int y,int w,int h)55 void sf_setclip(int x, int y, int w, int h)
56 {
57 	if (x < 0) {
58 		w += x;
59 		x = 0;
60 	}
61 	if (x + w > ewidth)
62 		w = ewidth - x;
63 	if (y < 0) {
64 		h += y;
65 		y = 0;
66 	}
67 	if (y + h > eheight)
68 		h = eheight - y;
69 	xmin = x;
70 	xmax = x + w;
71 	ymin = y;
72 	ymax = y + h;
73 }
74 
75 
sf_getclip(int * x,int * y,int * w,int * h)76 void sf_getclip(int *x, int *y, int *w, int *h)
77 {
78 	*x = xmin;
79 	*y = ymin;
80 	*w = xmax - xmin;
81 	*h = ymax - ymin;
82 }
83 
84 
mywcslen(zchar * b)85 static int mywcslen(zchar * b)
86 {
87 	int n = 0;
88 	while (*b++)
89 		n++;
90 	return n;
91 }
92 
93 
myGrefresh()94 static void myGrefresh()
95 {
96 	SDL_RenderClear(renderer);
97 	SDL_RenderCopy(renderer, texture, NULL, NULL);
98 	SDL_RenderPresent(renderer);
99 }
100 
101 
sf_wpixel(int x,int y,ulong c)102 void sf_wpixel(int x, int y, ulong c)
103 {
104 	if (x < xmin || x >= xmax || y < ymin || y >= ymax)
105 		return;
106 	sbuffer[x + sbpitch * y] = c;
107 	dirty = 1;
108 }
109 
110 
sf_rpixel(int x,int y)111 ulong sf_rpixel(int x, int y)
112 {
113 	if (x < 0 || x >= ewidth || y < 0 || y >= eheight)
114 		return 0;
115 	return sbuffer[x + sbpitch * y];
116 }
117 
118 #define MAXCUR 64
119 static ulong savedcur[MAXCUR];
120 
drawthecursor(int x,int y,int onoff)121 static void drawthecursor(int x, int y, int onoff)
122 {
123 	SF_textsetting *ts = sf_curtextsetting();
124 	int i, h = ts->font->height(ts->font);
125 	if (h > MAXCUR)
126 		h = MAXCUR;
127 	if (onoff) {
128 		for (i = 0; i < h; i++) {
129 			savedcur[i] = sf_rpixel(x, y + i);
130 			sf_wpixel(x, y + i, ts->fore);
131 		}
132 	} else {
133 		for (i = 0; i < h; i++) {
134 			sf_wpixel(x, y + i, savedcur[i]);
135 		}
136 	}
137 }
138 
139 
sf_IsValidChar(unsigned short c)140 bool sf_IsValidChar(unsigned short c)
141 {
142 	if (c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX)
143 		return true;
144 	if (c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX)
145 		return true;
146 	if (c >= 0x100)
147 		return true;
148 	return false;
149 }
150 
151 
sf_drawcursor(bool c)152 void sf_drawcursor(bool c)
153 {
154 	SF_textsetting *ts = sf_curtextsetting();
155 	drawthecursor(ts->cx, ts->cy, c);
156 }
157 
158 
sf_chline(int x,int y,ulong c,int n)159 void sf_chline(int x, int y, ulong c, int n)
160 {
161 	ulong *s;
162 	if (y < ymin || y >= ymax)
163 		return;
164 	if (x < xmin) {
165 		n += x - xmin;
166 		x = xmin;
167 	}
168 	if (x + n > xmax)
169 		n = xmax - x;
170 	if (n <= 0)
171 		return;
172 	s = sbuffer + x + sbpitch * y;
173 	while (n--)
174 		*s++ = c;
175 	dirty = 1;
176 }
177 
178 
sf_cvline(int x,int y,ulong c,int n)179 void sf_cvline(int x, int y, ulong c, int n)
180 {
181 	ulong *s;
182 	if (x < xmin || x >= xmax)
183 		return;
184 	if (y < xmin) {
185 		n += y - ymin;
186 		y = ymin;
187 	}
188 	if (y + n > ymax)
189 		n = ymax - y;
190 	if (n <= 0)
191 		return;
192 	s = sbuffer + x + sbpitch * y;
193 	while (n--) {
194 		*s = c;
195 		s += sbpitch;
196 	}
197 	dirty = 1;
198 }
199 
200 
sf_blendlinear(int a,ulong s,ulong d)201 ulong sf_blendlinear(int a, ulong s, ulong d)
202 {
203 	ulong r;
204 	r = ((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8;
205 	s >>= 8;
206 	d >>= 8;
207 	r |= (((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8) << 8;
208 	s >>= 8;
209 	d >>= 8;
210 	r |= (((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8) << 16;
211 	return r;
212 }
213 
214 
sf_writeglyph(SF_glyph * g)215 void sf_writeglyph(SF_glyph * g)
216 {
217 	SF_textsetting *ts = sf_curtextsetting();
218 
219 	int i, j, m;
220 
221 	int w = g->dx;
222 	int weff = g->xof + g->w;
223 	byte *bmp = (byte *) (&(g->bitmap[0]));
224 	int h = g->h;
225 	int nby = (g->w + 7) / 8;
226 	int byw = g->w;
227 
228 	int x = ts->cx;
229 	int y = ts->cy;
230 
231 	int dxpre = g->xof;
232 	int dypre = ts->font->ascent(ts->font) - h - (int)g->yof;
233 
234 	int height = ts->font->height(ts->font);
235 	int width;
236 
237 	ulong color, bc;
238 
239 	if ((ts->style & REVERSE_STYLE) != 0) {
240 		bc = ts->fore;
241 		color = ts->back;
242 	} else {
243 		color = ts->fore;
244 		bc = ts->back;
245 	}
246 
247 	/* compute size and position of background rect */
248 
249 	if (weff < w)
250 		weff = w;
251 	width = weff - ts->oh;
252 	if ((width > 0) && (ts->backTransparent == 0))
253 		sf_fillrect(bc, x + ts->oh, y, width, height);
254 
255 	x += dxpre;
256 	y += dypre;
257 
258 	for (i = 0; i < h; i++) {
259 		int xx = 0;
260 		if (ts->font->antialiased) {
261 			for (m = 0; m < byw; m++) {
262 				int t = *bmp++;
263 				if (xx < byw) {
264 					if (t) {
265 						ulong sval = color;
266 						if (t < 255)
267 							sval = sf_blend((int) (t + (t >> 7)), sval, sf_rpixel(x + xx, y));
268 						sf_wpixel(x + xx, y, sval);
269 					}
270 				}
271 				xx++;
272 			}
273 		} else {
274 			for (m = 0; m < nby; m++) {
275 				int t = *bmp++;
276 				for (j = 0; j < 8; j++, t *= 2) {
277 					if (xx < byw) {
278 						if (t & 0x80)
279 							sf_wpixel(x + xx, y, color);
280 					}
281 					xx++;
282 				}
283 			}
284 		}
285 		y++;
286 	}
287 
288 	ts->cx += (w);
289 	ts->oh = (weff > w) ? weff - w : 0;
290 }
291 
292 
sf_fillrect(unsigned long color,int x,int y,int w,int h)293 void sf_fillrect(unsigned long color, int x, int y, int w, int h)
294 {
295 	ulong *dst;
296 	int i;
297 	if (x < xmin) {
298 		w += x - xmin;
299 		x = xmin;
300 	}
301 	if (x + w > xmax)
302 		w = xmax - x;
303 	if (w <= 0)
304 		return;
305 	if (y < ymin) {
306 		h += y - ymin;
307 		y = ymin;
308 	}
309 	if (y + h > ymax)
310 		h = ymax - y;
311 	if (h <= 0)
312 		return;
313 	dst = sbuffer + x + sbpitch * y;
314 	while (h--) {
315 		for (i = 0; i < w; i++)
316 			dst[i] = color;
317 		dst += sbpitch;
318 	}
319 	dirty = 1;
320 }
321 
322 
sf_rect(unsigned long color,int x,int y,int w,int h)323 void sf_rect(unsigned long color, int x, int y, int w, int h)
324 {
325 	sf_chline(x, y, color, w);
326 	sf_chline(x, y + h - 1, color, w);
327 	sf_cvline(x, y, color, h);
328 	sf_cvline(x + w - 1, y, color, h);
329 }
330 
331 
sf_flushtext()332 void sf_flushtext()
333 {
334 	SF_textsetting *ts = sf_curtextsetting();
335 	ts->cx += ts->oh;
336 	ts->oh = 0;
337 }
338 
339 
340 /*
341  * os_erase_area
342  *
343  * Fill a rectangular area of the screen with the current background
344  * colour. Top left coordinates are (1,1). The cursor does not move.
345  *
346  * The final argument gives the window being changed, -1 if only a
347  * portion of a window is being erased, or -2 if the whole screen is
348  * being erased.
349  *
350  */
os_erase_area(int top,int left,int bottom,int right,int win)351 void os_erase_area(int top, int left, int bottom, int right, int win)
352 {
353 	sf_flushtext();
354 	sf_fillrect((sf_curtextsetting())->back, left - 1, top - 1,
355 		    right - left + 1, bottom - top + 1);
356 }
357 
358 
359 /*
360  * os_peek_colour
361  *
362  * Return the colour of the screen unit below the cursor. (If the
363  * interface uses a text mode, it may return the background colour
364  * of the character at the cursor position instead.) This is used
365  * when text is printed on top of pictures. Note that this coulor
366  * need not be in the standard set of Z-machine colours. To handle
367  * this situation, Frotz entends the colour scheme: Colours above
368  * 15 (and below 256) may be used by the interface to refer to non
369  * standard colours. Of course, os_set_colour must be able to deal
370  * with these colours.
371  *
372  */
os_peek_colour(void)373 int os_peek_colour(void)
374 {
375 	SF_textsetting *ts = sf_curtextsetting();
376 	sf_flushtext();
377 	return sf_GetColourIndex(sf_rpixel(ts->cx, ts->cy));
378 }
379 
380 
scroll(int x,int y,int w,int h,int n)381 static void scroll(int x, int y, int w, int h, int n)
382 {
383 	ulong *src, *dst;
384 	int nmove, step;
385 	if (n > 0) {
386 		dst = sbuffer + x + sbpitch * y;
387 		src = dst + n * sbpitch;
388 		nmove = h - n;
389 		step = sbpitch;
390 	} else if (n < 0) {
391 		n = -n;
392 		nmove = h - n;
393 		step = -sbpitch;
394 		src = sbuffer + x + sbpitch * (y + nmove - 1);
395 		dst = src + n * sbpitch;
396 	} else
397 		return;
398 	if (nmove > 0) {
399 		while (nmove--) {
400 			memmove(dst, src, w * sizeof(ulong));
401 			dst += step;
402 			src += step;
403 		}
404 		dirty = 1;
405 	}
406 }
407 
408 
409 /**
410  * Update the display if contents have changed.
411  * Return whether contents had changed, i.e., display was updated.
412  */
sf_flushdisplay()413 bool sf_flushdisplay()
414 {
415 	if (dirty) {
416 		SDL_UpdateTexture(texture, NULL, sbuffer,
417 				  sbpitch * sizeof(ulong));
418 		myGrefresh();
419 		dirty = 0;
420 		return true;
421 	} else
422 		return false;
423 }
424 
425 
426 /*
427  * os_scroll_area
428  *
429  * Scroll a rectangular area of the screen up (units > 0) or down
430  * (units < 0) and fill the empty space with the current background
431  * colour. Top left coordinates are (1,1). The cursor stays put.
432  *
433  */
os_scroll_area(int top,int left,int bottom,int right,int units)434 void os_scroll_area(int top, int left, int bottom, int right, int units)
435 {
436 	sf_flushtext();
437 
438 	scroll(left - 1, top - 1, right - left + 1, bottom - top + 1, units);
439 	if (units > 0)
440 		sf_fillrect((sf_curtextsetting())->back, left - 1,
441 			    bottom - units, right - left + 1, units);
442 	else if (units < 0)
443 		sf_fillrect((sf_curtextsetting())->back, left - 1, top - 1,
444 			    right - left + 1, units);
445 }
446 
447 
os_repaint_window(int win,int ypos_old,int ypos_new,int xpos,int ysize,int xsize)448 bool os_repaint_window(int win, int ypos_old, int ypos_new, int xpos,
449 		       int ysize, int xsize)
450 {
451 	/* TODO */
452 	return FALSE;
453 }
454 
455 
456 int SFdticks = 200;
457 volatile bool SFticked = 0;
458 static SDL_TimerID timerid = 0;
459 
mytimer(Uint32 inter,void * parm)460 static Uint32 mytimer(Uint32 inter, void *parm)
461 {
462 	SFticked = true;
463 	return inter;
464 }
465 
466 
cleanvideo()467 static void cleanvideo()
468 {
469 	if (timerid)
470 		SDL_RemoveTimer(timerid);
471 	SDL_Quit();
472 }
473 
474 #define RM 0x0000ff
475 #define GM 0x00ff00
476 #define BM 0xff0000
477 
sf_toggle_fullscreen()478 static void sf_toggle_fullscreen()
479 {
480 	if (SDL_SetWindowFullscreen
481 	    (window, isfullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP))
482 		os_warn("Error switching %s fullscreen: %s",
483 			isfullscreen ? "off" : "to", SDL_GetError());
484 	else {
485 		isfullscreen = !isfullscreen;
486 		if (!isfullscreen)
487 			SDL_SetWindowSize(window, AcWidth, AcHeight);
488 		myGrefresh();
489 	}
490 }
491 
492 
sf_initvideo(int W,int H,int full)493 void sf_initvideo(int W, int H, int full)
494 {
495 	Uint32 video_flags = SDL_WINDOW_RESIZABLE, pixfmt;
496 	Uint32 initflags = SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO;
497 
498 	sprintf(banner, "SDL Frotz v%s - %s (z%d)",
499 		VERSION, f_setup.story_name, z_header.version);
500 
501 	if (SDL_Init(initflags) < 0) {
502 		os_fatal("Couldn't initialize SDL: %s", SDL_GetError());
503 	}
504 
505 	sdl_active = TRUE;
506 
507 	/* We don't handle text edit events.  Not that I know why anyone would
508 	   want to use such an IME with Frotz. */
509 	SDL_SetHint(SDL_HINT_IME_INTERNAL_EDITING, "1");
510 
511 	CLEANREG(cleanvideo);
512 
513 	isfullscreen = full;
514 	if (full)
515 		video_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
516 	if ((window =
517 	     SDL_CreateWindow(banner, SDL_WINDOWPOS_UNDEFINED,
518 			      SDL_WINDOWPOS_UNDEFINED, W, H, video_flags)))
519 		renderer = SDL_CreateRenderer(window, -1, 0);
520 	else
521 		renderer = NULL;
522 	if (renderer == NULL) {
523 		os_fatal("Couldn't create %dx%d window: %s",
524 			 W, H, SDL_GetError());
525 	}
526 	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
527 	if (SDL_RenderSetLogicalSize(renderer, W, H))
528 		os_fatal("Failed to set logical rendering size to %dx%d: %s",
529 			 W, H, SDL_GetError());
530 	pixfmt = SDL_MasksToPixelFormatEnum(32, RM, GM, BM, 0);
531 	if (!(texture = SDL_CreateTexture(renderer, pixfmt,
532 					  SDL_TEXTUREACCESS_STREAMING, W, H)))
533 		os_fatal("Failed to create texture: %s", SDL_GetError());
534 
535 	sbuffer = calloc(W * H, sizeof(ulong));
536 	if (!sbuffer)
537 		os_fatal("Could not create gc");
538 
539 	SDL_AddTimer(SFdticks, mytimer, NULL);
540 
541 	xmin = ymin = 0;
542 	xmax = ewidth = W;
543 	ymax = eheight = H;
544 	sbpitch = W;
545 	dirty = 1;
546 }
547 
548 
549 /*
550  * os_draw_picture
551  *
552  * Display a picture at the given coordinates.
553  *
554  */
os_draw_picture(int picture,int y,int x)555 void os_draw_picture(int picture, int y, int x)
556 {
557 	int ew, eh, xx, yy, ix, iy, d;
558 	int ox, oy, ow, oh;
559 	Zwindow *winpars;
560 	sf_picture *pic = sf_getpic(picture);
561 	ulong *src, *dst, sval, dval, alpha;
562 
563 	sf_flushtext();
564 
565 	if (!pic)
566 		return;
567 	if (!pic->pixels)
568 		return;
569 	src = (ulong *) pic->pixels;
570 
571 	x--;
572 	y--;
573 	ew = ceil(m_gfxScale_w * pic->width);
574 	eh = ceil(m_gfxScale_h * pic->height);
575 
576 	/* this takes care of the fact that x, y are really 16 bit values */
577 	if (x & 0x8000)
578 		x |= 0xffff0000;
579 	if (y & 0x8000)
580 		y |= 0xffff0000;
581 
582 	/* get current window rect */
583 	sf_getclip(&ox, &oy, &ow, &oh);
584 	winpars = curwinrec();
585 	sf_setclip(winpars->x_pos - 1, winpars->y_pos - 1, winpars->x_size,
586 		   winpars->y_size);
587 
588 	/* clip taking into account possible origin
589 	 * outside the clipping rect */
590 	if (x < xmin) {
591 		d = xmin - x;
592 		ew -= d;
593 		x = xmin;
594 		src += d;
595 	}
596 	if (x + ew > xmax)
597 		ew = xmax - x;
598 	ew /= m_gfxScale_w;
599 
600 	if (y < ymin) {
601 		d = ymin - y;
602 		eh -= d;
603 		y = ymin;
604 		src += d * pic->width;
605 	}
606 	if (y + eh > ymax)
607 		eh = ymax - y;
608 	eh /= m_gfxScale_h;
609 
610 	sf_setclip(ox, oy, ow, oh);
611 
612 	if (ew <= 0)
613 		return;
614 	if (eh <= 0)
615 		return;
616 
617 	/* Use simple scaling without interpolation in palette mode. */
618 	/* Adapted from start of FrotzGfx::Paint() in Windows Frotz. */
619 	if (pic->usespalette) {
620 		if (!pic->adaptive && ApplyPalette(pic))
621 			sf_flushdisplay();
622 
623 		for (yy = 0; yy < eh * m_gfxScale_h; yy++) {
624 			int ys = ceil(yy / m_gfxScale_h);
625 			if (ys >= pic->height)
626 			    ys = pic->height - 1;
627 			for (xx = 0; xx < ew * m_gfxScale_w; xx++) {
628 				int xs = ceil(xx / m_gfxScale_w);
629 				if (xs >= pic->width)
630 				    xs = pic->width - 1;
631 				int index = pic->pixels[ys * pic->width + xs];
632 				if (index != pic->transparentcolor)
633 					sf_wpixel(x + xx, y + yy,
634 						  screen_palette[index]);
635 			}
636 		}
637 	} else {
638 		if (pic->adaptive)
639 			os_fatal("Adaptive images must be paletted");
640 
641 		for (yy = 0; yy < eh; yy++) {
642 			for (xx = 0; xx < ew; xx++) {
643 				dst =
644 				    sbuffer + x + (uint32_t)(xx * m_gfxScale_w) +
645 				    sbpitch * (y + (uint32_t)(yy * m_gfxScale_h));
646 				sval = src[xx];
647 				alpha = (sval >> 24);
648 				if (alpha == 255)
649 					dval = sval & 0xffffff;
650 				else
651 					dval =
652 					    sf_blend((int)
653 						     (alpha + (alpha >> 7)),
654 						     sval, dst[0]);
655 				for (iy = 0; iy < m_gfxScale_h; iy++) {
656 					for (ix = 0; ix < m_gfxScale_w; ix++)
657 						dst[ix] = dval;
658 					dst += sbpitch;
659 				}
660 			}
661 
662 			src += pic->width;
663 		}
664 	}
665 
666 	dirty = 1;
667 }
668 
669 
670 static ulong mytimeout;
671 int mouse_button;
672 static int numAltQ = 0;
673 
set_mouse_xy(int x,int y)674 static void set_mouse_xy(int x, int y)
675 {
676 	/* This is enough even in fullscreen:
677 	 * SDL maps mouse events to logical coordinates. */
678 	mouse_x = x + 1;
679 	mouse_y = y + 1;
680 }
681 
682 
683 /**
684  * Return the first character in the UTF-8-encoded str.
685  * Return 0 on encoding error or if the first character > U+FFFF.
686  */
decode_utf8(char * str)687 static zword decode_utf8(char *str)
688 {
689 	int i, n = 0;
690 	zword res;
691 	if (!(*str & 0200))
692 		return *str;
693 	if (!(*str & 0100))
694 		return 0;
695 	if (!(*str & 040)) {
696 		n = 2;
697 		res = *str & 037;
698 	} else if (!(*str & 020)) {
699 		n = 3;
700 		res = *str & 017;
701 	} else
702 		return 0;
703 	for (i = 1; i < n; ++i) {
704 		if ((str[i] & 0300) != 0200)
705 			return 0;
706 		res <<= 6;
707 		res |= (str[i] & 077);
708 	}
709 	return res;
710 }
711 
712 
handle_window_event(SDL_Event * e)713 static void handle_window_event(SDL_Event * e)
714 {
715 	switch (e->window.event) {
716 	case SDL_WINDOWEVENT_EXPOSED:
717 		myGrefresh();
718 	}
719 }
720 
721 
goodzkey(SDL_Event * e,int allowed)722 static zword goodzkey(SDL_Event * e, int allowed)
723 {
724 	SDL_Keycode c;
725 	zword res;
726 
727 	switch (e->type) {
728 	case SDL_QUIT:
729 		sf_quitconf();
730 		return 0;
731 	case SDL_MOUSEBUTTONDOWN:
732 		if (true) {
733 			mouse_button = e->button.button;
734 			set_mouse_xy(e->button.x, e->button.y);
735 			return ZC_SINGLE_CLICK;
736 		} else
737 			return 0;
738 	case SDL_KEYDOWN:
739 		if ((e->key.keysym.mod & 0xfff) == (KMOD_LALT | KMOD_LCTRL)
740 		    && e->key.keysym.sym == 'x')
741 			os_fatal("Emergency exit!\n\n(Control-Alt-X pressed)");
742 		if (e->key.keysym.mod & (KMOD_LALT | KMOD_RALT)) {
743 			if (e->key.keysym.sym == 'q') {
744 				numAltQ++;
745 				if (numAltQ > 2)
746 					os_fatal("Emergency exit!\n\n"
747 						 "(Alt-Q pressed 3 times in succession)");
748 				return 0;
749 			} else
750 				numAltQ = 0;
751 			if (e->key.keysym.sym == SDLK_RETURN) {
752 				sf_toggle_fullscreen();
753 				return 0;
754 			}
755 			if (allowed)
756 				switch (e->key.keysym.sym) {
757 				case 'x':
758 					return ZC_HKEY_QUIT;
759 				case 'p':
760 					return ZC_HKEY_PLAYBACK;
761 				case 'r':
762 					return ZC_HKEY_RECORD;
763 				case 's':
764 					return ZC_HKEY_SEED;
765 				case 'u':
766 					return ZC_HKEY_UNDO;
767 				case 'n':
768 					return ZC_HKEY_RESTART;
769 				case 'd':
770 					return ZC_HKEY_DEBUG;
771 				case 'h':
772 					return ZC_HKEY_HELP;
773 				}
774 			return 0;
775 		} else
776 			numAltQ = 0;
777 		switch (e->key.keysym.sym) {
778 		case SDLK_INSERT:
779 			return (allowed ? VK_INS : 0);
780 		case SDLK_DELETE:
781 			return (allowed ? VK_DEL : 0);
782 		case SDLK_BACKSPACE:
783 			return ZC_BACKSPACE;
784 		case SDLK_ESCAPE:
785 			return ZC_ESCAPE;
786 		case SDLK_RETURN:
787 			return ZC_RETURN;
788 		case SDLK_UP:
789 			return ZC_ARROW_UP;
790 		case SDLK_DOWN:
791 			return ZC_ARROW_DOWN;
792 		case SDLK_LEFT:
793 			return ZC_ARROW_LEFT;
794 		case SDLK_RIGHT:
795 			return ZC_ARROW_RIGHT;
796 		case SDLK_TAB:
797 			return (allowed ? VK_TAB : 0);
798 		case SDLK_PAGEUP:
799 			return (allowed ? VK_PAGE_UP : 0);
800 		case SDLK_PAGEDOWN:
801 			return (allowed ? VK_PAGE_DOWN : 0);
802 		case SDLK_KP_0:
803 			return ZC_NUMPAD_0;
804 		case SDLK_KP_1:
805 			return ZC_NUMPAD_1;
806 		case SDLK_KP_2:
807 			return ZC_NUMPAD_2;
808 		case SDLK_KP_3:
809 			return ZC_NUMPAD_3;
810 		case SDLK_KP_4:
811 			return ZC_NUMPAD_4;
812 		case SDLK_KP_5:
813 			return ZC_NUMPAD_5;
814 		case SDLK_KP_6:
815 			return ZC_NUMPAD_6;
816 		case SDLK_KP_7:
817 			return ZC_NUMPAD_7;
818 		case SDLK_KP_8:
819 			return ZC_NUMPAD_8;
820 		case SDLK_KP_9:
821 			return ZC_NUMPAD_9;
822 		case SDLK_F1:
823 			return ZC_FKEY_F1;
824 		case SDLK_F2:
825 			return ZC_FKEY_F2;
826 		case SDLK_F3:
827 			return ZC_FKEY_F3;
828 		case SDLK_F4:
829 			return ZC_FKEY_F4;
830 		case SDLK_F5:
831 			return ZC_FKEY_F5;
832 		case SDLK_F6:
833 			return ZC_FKEY_F6;
834 		case SDLK_F7:
835 			return ZC_FKEY_F7;
836 		case SDLK_F8:
837 			return ZC_FKEY_F8;
838 		case SDLK_F9:
839 			return ZC_FKEY_F9;
840 		case SDLK_F10:
841 			return ZC_FKEY_F10;
842 		case SDLK_F11:
843 			return ZC_FKEY_F11;
844 		case SDLK_F12:
845 			return ZC_FKEY_F12;
846 		}
847 		/* XXX Maybe we should just always have text input on. */
848 		if (!SDL_IsTextInputActive()) {
849 			c = e->key.keysym.sym;
850 			if (c >= 32 && c <= 126)
851 				return c;
852 		}
853 		return 0;
854 	case SDL_TEXTINPUT:
855 		res = decode_utf8(e->text.text);
856 		if ((res >= 32 && res <= 126) || res >= 160)
857 			return res;
858 		else
859 			return 0;
860 	case SDL_WINDOWEVENT:
861 		handle_window_event(e);
862 	}
863 	return 0;
864 }
865 
866 
sf_read_key(int timeout,bool cursor,bool allowed,bool text)867 zword sf_read_key(int timeout, bool cursor, bool allowed, bool text)
868 {
869 	SDL_Event event;
870 	zword inch = 0;
871 
872 	sf_flushtext();
873 	if (cursor)
874 		sf_drawcursor(true);
875 	sf_flushdisplay();
876 
877 	if (timeout)
878 		mytimeout = sf_ticks() + m_timerinterval * timeout;
879 	if (text)
880 		SDL_StartTextInput();
881 	while (true) {
882 		/* Get the next input */
883 		while (SDL_PollEvent(&event)) {
884 			if ((inch = goodzkey(&event, allowed)))
885 				break;
886 		}
887 		if (inch)
888 			break;
889 		if ((timeout) && (sf_ticks() >= mytimeout)) {
890 			inch = ZC_TIME_OUT;
891 			break;
892 		}
893 		sf_checksound();
894 		sf_sleep(10);
895 	}
896 	if (text)
897 		SDL_StopTextInput();
898 
899 	if (cursor)
900 		sf_drawcursor(false);
901 
902 	return inch;
903 }
904 
905 
906 /*
907  * os_read_key
908  *
909  * Read a single character from the keyboard (or a mouse click) and
910  * return it. Input aborts after timeout/10 seconds.
911  *
912  */
os_read_key(int timeout,int cursor)913 zchar os_read_key(int timeout, int cursor)
914 {
915 	return sf_read_key(timeout, cursor, false, true);
916 }
917 
918 
919 /*
920  * os_read_line
921  *
922  * Read a line of input from the keyboard into a buffer. The buffer
923  * may already be primed with some text. In this case, the "initial"
924  * text is already displayed on the screen. After the input action
925  * is complete, the function returns with the terminating key value.
926  * The length of the input should not exceed "max" characters plus
927  * an extra 0 terminator.
928  *
929  * Terminating keys are the return key (13) and all function keys
930  * (see the Specification of the Z-machine) which are accepted by
931  * the is_terminator function. Mouse clicks behave like function
932  * keys except that the mouse position is stored in global variables
933  * "mouse_x" and "mouse_y" (top left coordinates are (1,1)).
934  *
935  * Furthermore, Frotz introduces some special terminating keys:
936  *
937  *     ZC_HKEY_PLAYBACK (Alt-P)
938  *     ZC_HKEY_RECORD (Alt-R)
939  *     ZC_HKEY_SEED (Alt-S)
940  *     ZC_HKEY_UNDO (Alt-U)
941  *     ZC_HKEY_RESTART (Alt-N, "new game")
942  *     ZC_HKEY_QUIT (Alt-X, "exit game")
943  *     ZC_HKEY_DEBUG (Alt-D)
944  *     ZC_HKEY_HELP (Alt-H)
945  *
946  * If the timeout argument is not zero, the input gets interrupted
947  * after timeout/10 seconds (and the return value is 0).
948  *
949  * The complete input line including the cursor must fit in "width"
950  * screen units.
951  *
952  * The function may be called once again to continue after timeouts,
953  * misplaced mouse clicks or hot keys. In this case the "continued"
954  * flag will be set. This information can be useful if the interface
955  * implements input line history.
956  *
957  * The screen is not scrolled after the return key was pressed. The
958  * cursor is at the end of the input line when the function returns.
959  *
960  * Since Frotz 2.2 the helper function "completion" can be called
961  * to implement word completion (similar to tcsh under Unix).
962  *
963  */
os_read_line(int max,zchar * buf,int timeout,int width,int continued)964 zchar os_read_line(int max, zchar * buf, int timeout, int width, int continued)
965 {
966 	static int pos = 0, searchpos = -1;
967 	int ptx, pty;
968 	int len = mywcslen(buf);
969 	SF_textsetting *ts = sf_curtextsetting();
970 	SDL_Event event;
971 	sf_flushtext();
972 
973 	/* Better be careful here or it might segv.  I wonder if we should just
974 	   ignore 'continued' and check for len > 0 instead?  Might work better
975 	   with Beyond Zork. */
976 	if (!continued || pos > len || searchpos > len) {
977 		pos = len;
978 		gen_history_reset();	/* Reset user's history view. */
979 		searchpos = -1;	/* -1 means initialize from len. */
980 	}
981 	/* Draw the input line */
982 	ptx = ts->cx;
983 	pty = ts->cy;
984 	ptx -= os_string_width(buf);
985 	sf_DrawInput(buf, pos, ptx, pty, width, true);
986 
987 	if (timeout)
988 		mytimeout = sf_ticks() + m_timerinterval * timeout;
989 	SDL_StartTextInput();
990 	while (true) {
991 		/* Get the next input */
992 		while (SDL_PollEvent(&event)) {
993 			zword c;
994 			if ((c = goodzkey(&event, 1))) {
995 				switch (c) {
996 				case ZC_BACKSPACE:
997 					/* Delete the character to the left of the cursor */
998 					if (pos > 0) {
999 						memmove(buf + pos - 1,
1000 							buf + pos,
1001 							sizeof(zchar) *
1002 							(mywcslen(buf) - pos +
1003 							 1));
1004 						pos--;
1005 						sf_DrawInput(buf, pos, ptx, pty,
1006 							     width, true);
1007 					}
1008 					continue;
1009 				case VK_DEL:
1010 					/* Delete the character to the right of the cursor */
1011 					if (pos < mywcslen(buf)) {
1012 						memmove(buf + pos,
1013 							buf + pos + 1,
1014 							sizeof(zchar) *
1015 							(mywcslen(buf) - pos));
1016 						sf_DrawInput(buf, pos, ptx, pty,
1017 							     width, true);
1018 					}
1019 					continue;
1020 				case ZC_ESCAPE:	/* Delete whole line */
1021 					pos = 0;
1022 					buf[0] = '\0';
1023 					searchpos = -1;
1024 					gen_history_reset();
1025 					sf_DrawInput(buf, pos, ptx, pty, width,
1026 						     true);
1027 					continue;
1028 				case VK_TAB:
1029 					if (pos == (int)mywcslen(buf)) {
1030 						zchar extension[10], *s;
1031 						completion(buf, extension);
1032 
1033 						/* Add the completion to the input stream */
1034 						for (s = extension; *s != 0;
1035 						     s++)
1036 							if (sf_IsValidChar(*s))
1037 								buf[pos++] =
1038 								    (*s);
1039 						buf[pos] = 0;
1040 						sf_DrawInput(buf, pos, ptx, pty,
1041 							     width, true);
1042 					}
1043 					continue;
1044 				case ZC_ARROW_LEFT:
1045 					/* Move the cursor left */
1046 					if (pos > 0)
1047 						pos--;
1048 					sf_DrawInput(buf, pos, ptx, pty, width,
1049 						     true);
1050 					continue;
1051 				case ZC_ARROW_RIGHT:
1052 					/* Move the cursor right */
1053 					if (pos < (int)mywcslen(buf))
1054 						pos++;
1055 					sf_DrawInput(buf, pos, ptx, pty, width,
1056 						     true);
1057 					continue;
1058 				case ZC_ARROW_UP:
1059 				case ZC_ARROW_DOWN:
1060 					if (searchpos < 0)
1061 						searchpos = mywcslen(buf);
1062 					if ((c == ZC_ARROW_UP
1063 					     ? gen_history_back :
1064 					     gen_history_forward) (buf,
1065 								   searchpos,
1066 								   max)) {
1067 						pos = mywcslen(buf);
1068 						sf_DrawInput(buf, pos, ptx, pty,
1069 							     width, true);
1070 					}
1071 					continue;
1072 					/* Pass through as up/down arrows for Beyond Zork. */
1073 				case VK_PAGE_UP:
1074 					c = ZC_ARROW_UP;
1075 					break;
1076 				case VK_PAGE_DOWN:
1077 					c = ZC_ARROW_DOWN;
1078 					break;
1079 				default:
1080 					if (sf_IsValidChar(c)
1081 					    && mywcslen(buf) < max) {
1082 						/* Add a valid character to the input line
1083 						 * Get the width of the new input line */
1084 						int len = os_string_width(buf);
1085 						len += os_char_width(c);
1086 						len += os_char_width('0');
1087 
1088 						/* printf("l%d w%d p%d\n",len,width,pos);
1089 						 * Only allow if the width limit is not exceeded */
1090 						if (len <= width) {
1091 							memmove(buf + pos + 1,
1092 								buf + pos,
1093 								sizeof(zchar) *
1094 								(mywcslen(buf) -
1095 								 pos + 1));
1096 							*(buf + pos) = c;
1097 							pos++;
1098 							sf_DrawInput(buf, pos,
1099 								     ptx, pty,
1100 								     width,
1101 								     true);
1102 						}
1103 						continue;
1104 					}
1105 				}
1106 				if (is_terminator(c)) {
1107 					/* Terminate the current input */
1108 					m_exitPause = false;
1109 					sf_DrawInput(buf, pos, ptx, pty, width,
1110 						     false);
1111 
1112 					if ((c == ZC_SINGLE_CLICK)
1113 					    || (c == ZC_DOUBLE_CLICK)) {
1114 						/*  mouse_x = input.mousex+1;
1115 						   mouse_y = input.mousey+1; */
1116 					} else if (c == ZC_RETURN)
1117 						gen_add_to_history(buf);
1118 					SDL_StopTextInput();
1119 					return c;
1120 				}
1121 			}
1122 		}
1123 		if ((timeout) && (sf_ticks() >= mytimeout)) {
1124 			SDL_StopTextInput();
1125 			return ZC_TIME_OUT;
1126 		}
1127 		sf_checksound();
1128 		sf_sleep(10);
1129 	}
1130 }
1131 
1132 /* Draw the current input line */
sf_DrawInput(zchar * buffer,int pos,int ptx,int pty,int width,bool cursor)1133 void sf_DrawInput(zchar * buffer, int pos, int ptx, int pty, int width,
1134 		  bool cursor)
1135 {
1136 	int height;
1137 	SF_textsetting *ts = sf_curtextsetting();
1138 
1139 
1140 	height = ts->font->height(ts->font);
1141 
1142 	/* Remove any previous input */
1143 	sf_fillrect(ts->back, ptx, pty, width, height);
1144 
1145 	/* Display the input */
1146 	ts->cx = ptx;
1147 	ts->cy = pty;
1148 	os_display_string(buffer);
1149 
1150 	if (cursor) {
1151 		int wid = 0, i = 0, oh;
1152 		while (i < pos)
1153 			wid += sf_charwidth(buffer[i++], &oh);
1154 		drawthecursor(ptx + wid, pty, 1);
1155 	}
1156 
1157 	/* Update the window */
1158 	sf_flushdisplay();
1159 }
1160 
1161 
1162 /*
1163  * os_read_mouse
1164  *
1165  * Store the mouse position in the global variables "mouse_x" and
1166  * "mouse_y", the code of the last clicked menu in "menu_selected"
1167  * and return the mouse buttons currently pressed.
1168  *
1169  */
os_read_mouse(void)1170 zword os_read_mouse(void)
1171 {
1172 	byte c;
1173 	int x, y;
1174 	zword btn = 0;
1175 	/* Get the mouse position */
1176 	SDL_PumpEvents();
1177 	c = SDL_GetMouseState(&x, &y);
1178 	set_mouse_xy(x, y);
1179 	/* Get the last selected menu item */
1180 	/* menu_selected = theWnd->GetMenuClick();*/
1181 
1182 	/* Get the mouse buttons */
1183 	if (c & SDL_BUTTON_LMASK)
1184 		btn |= 1;
1185 	if (c & SDL_BUTTON_RMASK)
1186 		btn |= 2;
1187 	if (c & SDL_BUTTON_MMASK)
1188 		btn |= 4;
1189 
1190 	return btn;
1191 }
1192 
1193 
1194 /*
1195  * os_more_prompt
1196  *
1197  * Display a MORE prompt, wait for a keypress and remove the MORE
1198  * prompt from the screen.
1199  *
1200  */
os_more_prompt(void)1201 void os_more_prompt(void)
1202 {
1203 	if (m_morePrompts) {
1204 		SF_textsetting *ts;
1205 		int x, y, h;
1206 		const char *p = sf_msgstring(IDS_MORE);
1207 		sf_flushtext();
1208 
1209 		/* Save the current text position */
1210 		sf_pushtextsettings();
1211 		ts = sf_curtextsetting();
1212 		x = ts->cx;
1213 		y = ts->cy;
1214 		h = ts->font->height(ts->font);
1215 		/* Show a [More] prompt */
1216 		while (*p)
1217 			os_display_char((zchar) (*p++));
1218 
1219 		/* Wait for a key press */
1220 		sf_read_key(0, true, false, false);
1221 		/* Remove the [More] prompt */
1222 		sf_fillrect(ts->back, x, y, ts->cx - x, h);
1223 
1224 		/* Restore the current text position */
1225 		sf_poptextsettings();
1226 	}
1227 }
1228 
1229 
sf_savearea(int x,int y,int w,int h)1230 ulong *sf_savearea(int x, int y, int w, int h)
1231 {
1232 	ulong *r, *p, *s;
1233 	int i;
1234 
1235 	if (x < 0) {
1236 		w += x;
1237 		x = 0;
1238 	}
1239 	if (x + w > ewidth)
1240 		w = ewidth - x;
1241 	if (w <= 0)
1242 		return NULL;
1243 
1244 	if (y < 0) {
1245 		h += y;
1246 		y = 0;
1247 	}
1248 	if (y + h > eheight)
1249 		h = eheight - y;
1250 	if (h <= 0)
1251 		return NULL;
1252 
1253 	r = p = malloc((w * h + 4) * sizeof(ulong));
1254 	if (!r)
1255 		return NULL;
1256 
1257 	*p++ = x;
1258 	*p++ = y;
1259 	*p++ = w;
1260 	*p++ = h;
1261 
1262 	s = sbuffer + x + y * sbpitch;
1263 	for (i = 0; i < h; i++) {
1264 		memmove(p, s, w * sizeof(ulong));
1265 		p += w;
1266 		s += sbpitch;
1267 	}
1268 	return r;
1269 }
1270 
1271 
sf_restoreareaandfree(ulong * s)1272 void sf_restoreareaandfree(ulong * s)
1273 {
1274 	ulong *p, *d;
1275 	int i, x, y, w, h;
1276 	if (!s)
1277 		return;
1278 
1279 	p = s;
1280 	x = *p++;
1281 	y = *p++;
1282 	w = *p++;
1283 	h = *p++;
1284 
1285 	d = sbuffer + x + y * sbpitch;
1286 	for (i = 0; i < h; i++) {
1287 		memmove(d, p, w * sizeof(ulong));
1288 		p += w;
1289 		d += sbpitch;
1290 	}
1291 
1292 	free(s);
1293 	dirty = 1;
1294 	sf_flushdisplay();
1295 }
1296 
1297 
1298 int (*sf_osdialog)(bool ex, const char *def, const char *filt, const char *tit,
1299 		   char **res, ulong * sbuf, int sbp, int ew, int eh,
1300 		   int isfull) = NULL;
1301 
1302 
sf_user_fdialog(bool existing,const char * defaultname,const char * filter,const char * title,char ** result)1303 int sf_user_fdialog(bool existing, const char *defaultname, const char *filter,
1304 		    const char *title, char **result)
1305 {
1306 	if (sf_osdialog)
1307 		return sf_osdialog(existing, defaultname, filter, title, result,
1308 				   sbuffer, sbpitch, ewidth, eheight,
1309 				   isfullscreen);
1310 	return SF_NOTIMP;
1311 }
1312 
1313 
sf_videodata(ulong ** sb,int * sp,int * ew,int * eh)1314 void sf_videodata(ulong ** sb, int *sp, int *ew, int *eh)
1315 {
1316 	*sb = sbuffer;
1317 	*sp = sbpitch;
1318 	*ew = ewidth;
1319 	*eh = eheight;
1320 }
1321 
1322 
1323 extern zword sf_yesnooverlay(int xc, int yc, char *t, int sr);
sf_quitconf()1324 static void sf_quitconf()
1325 {
1326 	if (sf_yesnooverlay(ewidth / 2, eheight / 2, "Quit: are you sure?", 1)
1327 	    == ZC_RETURN) {
1328 		printf
1329 		    ("\n\nQuitting (close button clicked on main window)\n\n");
1330 		SDL_Quit();
1331 		os_quit(EXIT_SUCCESS);
1332 	}
1333 }
1334 
1335 
os_tick()1336 void os_tick()
1337 {
1338 	sf_checksound();
1339 	if (SFticked) {
1340 		SFticked = false;
1341 		if (!sf_flushdisplay()) {
1342 			SDL_Event ev;
1343 			SDL_PumpEvents();
1344 			while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT,
1345 					      SDL_WINDOWEVENT,
1346 					      SDL_WINDOWEVENT) > 0)
1347 				handle_window_event(&ev);
1348 		}
1349 	}
1350 }
1351 
1352 
1353 /* Apply the picture's palette to the screen palette. */
1354 /* Adapted from FrotzGfx::ApplyPalette() in Windows Frotz. */
ApplyPalette(sf_picture * graphic)1355 static bool ApplyPalette(sf_picture * graphic)
1356 {
1357 	bool changed = FALSE;
1358 	int i, colors;
1359 
1360 	memset(&screen_palette, 0, sizeof(ulong));
1361 
1362 	if (graphic->usespalette) {
1363 		colors = graphic->palette_entries;
1364 		if (colors > 16)
1365 			colors = 16;
1366 		for (i = 0; i < colors; i++) {
1367 			if (screen_palette[i] != graphic->palette[i]) {
1368 				changed = TRUE;
1369 				screen_palette[i] = graphic->palette[i];
1370 			}
1371 		}
1372 	}
1373 	return changed;
1374 }
1375