1 /***************************************************************************
2                           sdltools.cpp  -  description
3                              -------------------
4     begin                : Fri Jul 21 2000
5     copyright            : (C) 2006 by Immi
6     email                : cuyo@pcpool.mathematik.uni-freiburg.de
7 
8 Modified 2006-2008,2010,2011,2014 by the cuyo developers
9 
10  ***************************************************************************/
11 
12 /***************************************************************************
13  *                                                                         *
14  *   This program is free software; you can redistribute it and/or modify  *
15  *   it under the terms of the GNU General Public License as published by  *
16  *   the Free Software Foundation; either version 2 of the License, or     *
17  *   (at your option) any later version.                                   *
18  *                                                                         *
19  ***************************************************************************/
20 
21 #include <cstdlib>
22 
23 #include "stringzeug.h"
24 #include "sdltools.h"
25 #include "global.h"
26 #include "fehler.h"
27 #include "layout.h"
28 #include "bilddatei.h"
29 
30 
31 /* Right now, the picture scale funcition still has a fixed
32    scale_base=4 build in. Apart from that, it should be possible
33    to increase scale_base to 8 or 16 (so that more different scalings
34    are possible) */
35 
36 #define videomode_flags_w \
37   (SDL_SWSURFACE/*|SDL_DOUBLEBUF*/|SDL_ANYFORMAT|SDL_VIDEORESIZE)
38 #define videomode_flags_f \
39   (SDL_SWSURFACE/*|SDL_DOUBLEBUF*/|SDL_ANYFORMAT|SDL_FULLSCREEN)
40 
41 
42 
43 namespace Area {
44   void init();
45 }
46 
47 
48 
49 namespace SDLTools {
50 
51 
52 /* A 32-Bit-surface, to copy the pixel format from */
53 SDL_Surface * gSampleSurface32 = 0;
54 
55 /* The pixel format used for maskedDisplayFormat() */
56 SDL_PixelFormat gMaskedFormat;
57 /* The replacement colour for the ColourKey, if necessary */
58 Uint8 gColkeyErsatz;
59 
60 /* gScale == scale_base means original size */
61 int gScale;
62 int gShiftX, gShiftY;
63 
64 /* Virtual Window-size, i.e. size how it looks for the cuyo program
65    (In contrast to screen size, which may be scaled).
66    Start with sensible default size. */
67 int gVirtualWidth = L_usual_width;
68 int gVirtualHeight = L_usual_height;
69 
70 bool gFullScreen = false;
71 
72 
myQuitSDL()73 void myQuitSDL() {
74   if (gSampleSurface32)
75     SDL_FreeSurface(gSampleSurface32);
76   if (gMaskedFormat.palette) {
77     delete[] gMaskedFormat.palette->colors;
78     delete gMaskedFormat.palette;
79   }
80   SDL_Quit();
81 }
82 
83 
84 
85 /* Compute the value of gScale for a window of size w x h */
getScale(int w,int h)86 int getScale(int w, int h) {
87   int sc_w = w / (gVirtualWidth / scale_base);
88   int sc_h = h / (gVirtualHeight / scale_base);
89   int sc = sc_w < sc_h ? sc_w : sc_h;
90   if (sc < 1) sc = 1;
91   return sc;
92 }
93 
94 
computeScaleAndShift()95 void computeScaleAndShift() {
96   SDL_Surface * s = SDL_GetVideoSurface();
97   gScale = getScale(s->w, s->h);
98   gShiftX = (s->w - gVirtualWidth * gScale / scale_base) / 2;
99   gShiftY = (s->h - gVirtualHeight * gScale / scale_base) / 2;
100 
101   /* Rescale all icons */
102   Bilddatei::resizeEvent();
103 }
104 
105 
106 
107 /* In fullscreen mode: find out optimal resolution to choose;
108    (this is also used to get an idea on the optimal window size
109    in windowed mode);
110    returns false, if there seem to be no fullscreen mode available */
fullscreenSize(int & w,int & h)111 bool fullscreenSize(int & w, int & h) {
112   SDL_Rect **modes;
113   /* format = 0: search for best video mode */
114   modes = SDL_ListModes(0, videomode_flags_f);
115 
116   /* No size possible ?!? Let's hope that this is just because there
117      is no fullscreen mode available. (As fullscreenSize() is called
118      even in windowed mode, it is important that we do not just crash.) */
119   if (modes == (SDL_Rect**) 0) {
120     /* In case we just tried to guess a good window size, let's
121        just return a value which we hope to be good. */
122     w = L_preferred_width;
123     h = L_preferred_height;
124     return false;
125   }
126 
127   /* Any size possible in fullscreen mode... */
128   if (modes == (SDL_Rect**) -1) {
129     w = L_preferred_width;
130     h = L_preferred_height;
131   }
132 
133   /* Return the smallest resolution which is as least as
134      big as L_usual_width x L_usual_height,
135      or otherwise the biggest existing one */
136   bool found = false;
137   for (int i = 0; modes[i]; i++) {
138     if (modes[i]->w >= L_usual_width && modes[i]->h >= L_usual_height  ||  !found) {
139       w = modes[i]->w;
140       h = modes[i]->h;
141       found = true;
142     }
143   }
144   return true;
145 }
146 
147 
148 /* Set/change (screen, i.e. real) window size */
setVideoMode(int w,int h)149 void setVideoMode(int w, int h) {
150 
151   /* In fullscreen mode or if no window size was given, find
152      a good default window size */
153   if (gFullScreen || w == -1) {
154     if (!fullscreenSize(w, h)) {
155       /* No fullscreen mode available? Then fall back to windowed
156          mode. */
157       gFullScreen = false;
158 
159     } else if (!gFullScreen) {
160       /* Not fullscreen? Then use the resolution which would be optimal
161          in fullscreen mode, but shrink the window to get rid of the
162          black borders */
163       int sc = getScale(w, h);
164       int pref_w = L_preferred_width * sc / scale_base;
165       int pref_h = L_preferred_height * sc / scale_base;
166       if (w > pref_w) w = pref_w;
167       if (h > pref_h) h = pref_h;
168     }
169   }
170 
171   /* Initialize the display
172      requesting a software surface
173      BitsPerPixel = 0: Take the current BitsPerPixel
174      SDL_ANYFORMAT: Other pixel depths are ok, too. */
175   SDL_Surface * s = SDL_SetVideoMode(w, h, 0,
176         gFullScreen ? videomode_flags_f : videomode_flags_w);
177   SDLASSERT(s);
178 
179   computeScaleAndShift();
180 }
181 
182 
computeMaskedFormat()183 void computeMaskedFormat() {
184 
185   CASSERT(gSampleSurface32);
186 
187   SDL_Surface * s = SDL_GetVideoSurface();
188 
189   gMaskedFormat = *(s->format);
190   if (gMaskedFormat.palette) {
191     int ncolours = gMaskedFormat.palette->ncolors;
192 
193     /* Erst mal eine Kopie der Palette anlegen. B�se Dinge k�nnten passieren,
194        wenn das displaysurface sie �ndert. */
195     SDL_Color * neufarben = new SDL_Color[ncolours];
196     memcpy((void*) neufarben, (void*) gMaskedFormat.palette->colors,
197 	   ncolours * sizeof(SDL_Color));
198     SDL_Palette * neupal = new SDL_Palette;
199     neupal->ncolors = ncolours;
200     neupal->colors = neufarben;
201     gMaskedFormat.palette = neupal;
202 
203     if (ncolours < 255) {
204       gMaskedFormat.colorkey = ncolours + 1;
205       gColkeyErsatz = 0;  // Wird eh nicht benutzt...
206     }
207     else {
208       /* Jetzt m�ssen wir eine entbehrliche Farbe suchen */
209       int dmin = 1000;
210       int c1best=0, c2best=0;
211         // Initialisierung ist unn�tig, spart aber Warnungen
212 
213       SDL_Color * colours = gMaskedFormat.palette->colors;
214 
215       #ifdef DIST
216         #error Vorsicht, DIST gibt es schon!
217       #endif
218       #define DIST(dim) (colours[c1].dim<colours[c2].dim \
219         ? colours[c2].dim-colours[c1].dim                \
220 	: colours[c1].dim-colours[c2].dim)
221 
222       for (int c1=0; c1<ncolours; c1++)
223 	for (int c2=c1+1; c2<ncolours; c2++) {
224 	  int d = DIST(r)+DIST(g)+DIST(b);
225 	  if (d<dmin) {
226 	    dmin=d;
227 	    c1best=c1;
228 	    c2best=c2;
229 	  }
230 	}
231 
232       #undef DIST
233 
234       gMaskedFormat.colorkey = c2best;
235       gColkeyErsatz = c1best;
236     }
237   }
238   else
239     gMaskedFormat = *(gSampleSurface32->format);
240 }
241 
242 
243 /* opt_w, opt_h: size of window, as given by command line option;
244    or -1,-1 to automatically choose window size */
initSDL(int opt_w,int opt_h)245 void initSDL(int opt_w, int opt_h) {
246 
247   /* Initialize the SDL library */
248   /* Audio will be initialized by our sound system */
249   SDLASSERT(SDL_Init(SDL_INIT_VIDEO) == 0);
250 
251   /* Clean up on exit */
252   atexit(myQuitSDL);
253 
254   /* Set the name of the window (and the icon) */
255   setMainTitle();
256 
257   setVideoMode(opt_w, opt_h);
258 
259   gSampleSurface32 = createSurface32(1, 1);
260   SDLASSERT(gSampleSurface32);
261 
262   computeMaskedFormat();
263 
264 
265   SDL_EventState(SDL_KEYUP, SDL_IGNORE);
266   SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE);
267 
268   /* We need the characters corresponding to key-events for the menus:
269      For example, on a French keyboard, shift-& is 1 */
270   SDL_EnableUNICODE(1);
271 
272   Area::init();
273 }
274 
275 
276 
277 
278 /* Call while cuyo is already running is not yet supported
279    (but not difficult to implement) */
setFullscreen(bool fs)280 void setFullscreen(bool fs) {
281   gFullScreen = fs;
282 }
283 
284 
285 /* Change the size of the window from the view of the cuyo program.
286    Does *not* change the real window size; instead, scaling may
287    change.
288    I propose that this should only be used in such a way that when
289    the real window size is the preferred one (L_preferred_xxx),
290    then scaling should never change.
291    Note that changing the scaling always takes some time.
292 */
setVirtualWindowSize(int w,int h)293 void setVirtualWindowSize(int w, int h) {
294   gVirtualWidth = w;
295   gVirtualHeight = h;
296   computeScaleAndShift();
297 }
298 
299 
setWindowTitle(const char * title)300 void setWindowTitle(const char * title) {
301   char * title_ = convert_for_window_title(title);
302   char * icon = convert_for_window_title("Cuyo");
303   SDL_WM_SetCaption(title_,icon);
304   free(title_);
305   free(icon);
306 }
307 
setMainTitle()308 void setMainTitle() {
309   // TRANSLATORS: "cuyo" is the program's name
310   setWindowTitle(gDebug ? _("Cuyo - debug mode") : "Cuyo");
311 }
312 
setLevelTitle(const Str & levelname)313 void setLevelTitle(const Str & levelname) {
314   // TRANSLATORS: This is a window title
315   setWindowTitle(_sprintf(_("Cuyo - level %s"),levelname.data()).data());
316 }
317 
318 /* Convert Qt-Key into SDL-Key; don't use Qt constants: we don't want to depend on
319    Qt just to be able to read old .cuyo files. */
qtKey2sdlKey(int qtk)320 SDLKey qtKey2sdlKey(int qtk) {
321 
322   /* Letters are uppercase in Qt and lowercase in SDL */
323   if (qtk >= 'A' && qtk <= 'Z')
324     return (SDLKey) (qtk - 'A' + 'a');
325 
326   /* Don't change other Ascii Characters.
327      (Maybe �, �, �, etc are a problem) */
328   if (qtk <= 255)
329     return (SDLKey) qtk;
330 
331   /* Other important keys */
332   switch (qtk) {
333     case 0x1000: return SDLK_ESCAPE;
334     case 0x1001: return SDLK_TAB;
335     case 0x1003: return SDLK_BACKSPACE;
336     case 0x1004: return SDLK_RETURN;
337     case 0x1006: return SDLK_INSERT;
338     case 0x1007: return SDLK_DELETE;
339     case 0x1008: return SDLK_PAUSE;
340     case 0x1009: return SDLK_PRINT;
341     case 0x100a: return SDLK_SYSREQ;
342     case 0x100b: return SDLK_CLEAR;
343     case 0x1010: return SDLK_HOME;
344     case 0x1011: return SDLK_END;
345     case 0x1012: return SDLK_LEFT;
346     case 0x1013: return SDLK_UP;
347     case 0x1014: return SDLK_RIGHT;
348     case 0x1015: return SDLK_DOWN;
349     case 0x1016: return SDLK_PAGEUP;
350     case 0x1017: return SDLK_PAGEDOWN;
351 
352     case 0x1030: return SDLK_F1;
353     case 0x1031: return SDLK_F2;
354     case 0x1032: return SDLK_F3;
355     case 0x1033: return SDLK_F4;
356     case 0x1034: return SDLK_F5;
357     case 0x1035: return SDLK_F6;
358     case 0x1036: return SDLK_F7;
359     case 0x1037: return SDLK_F8;
360     case 0x1038: return SDLK_F9;
361     case 0x1039: return SDLK_F10;
362     case 0x103a: return SDLK_F11;
363     case 0x103b: return SDLK_F12;
364     case 0x103c: return SDLK_F13;
365     case 0x103d: return SDLK_F14;
366     case 0x103e: return SDLK_F15;
367 
368     case 0x1055: return SDLK_MENU;
369     case 0x1058: return SDLK_HELP;
370     //case : return SDLK_BREAK;
371     //case : return SDLK_POWER;
372     //case : return SDLK_EURO;
373     //case : return SDLK_UNDO;
374     default: return SDLK_UNKNOWN;
375   }
376 
377   /* Modifier keys are missing */
378 }
379 
380 
381 
rect(int x,int y,int w,int h)382 SDL_Rect rect(int x, int y, int w, int h) {
383   SDL_Rect ret;
384   ret.x = x; ret.y = y; ret.w = w; ret.h = h;
385   return ret;
386 }
387 
intersection(const SDL_Rect & a,const SDL_Rect & b,SDL_Rect & ret)388 bool intersection(const SDL_Rect & a, const SDL_Rect & b, SDL_Rect & ret) {
389   ret.x = a.x > b.x ? a.x : b.x;
390   ret.y = a.y > b.y ? a.y : b.y;
391   int xplusw = a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w;
392   int yplush = a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h;
393   if (xplusw<=ret.x || yplush<=ret.y)
394     return false;
395   ret.w = xplusw-ret.x;
396   ret.h = yplush-ret.y;
397   return true;
398 }
399 
400 
401 /* Returns true if a is contained in b */
contained(const SDL_Rect & a,const SDL_Rect & b)402 bool contained(const SDL_Rect & a, const SDL_Rect & b) {
403   return a.x >= b.x && a.y >= b.y &&
404       a.x + a.w <= b.x + b.w && a.y + a.h <= b.y + b.h;
405 }
406 
407 
408 /* Creates a 32-bit-surface with alpha. After filling it with your
409    data, you should convert it to screen format */
createSurface32(int w,int h)410 SDL_Surface * createSurface32(int w, int h) {
411 
412   union { Uint32 f; Uint8 k[4];} rmask, gmask, bmask, amask;
413 
414   /* Die richtigen Bits der Masken auf 1 setzen; das Problem ist, dass
415      welches die richtigen Bits sind von der Endianness abhaengen.
416      Das folgende macht's richtig: */
417   rmask.f = gmask.f = bmask.f = amask.f = 0;
418   rmask.k[0] = gmask.k[1] = bmask.k[2] = amask.k[3] = 0xff;
419 
420   SDL_Surface * s = SDL_CreateRGBSurface(SDL_HWSURFACE, w, h, 32, rmask.f, gmask.f, bmask.f, amask.f);
421   SDLASSERT(s);
422   return s;
423 }
424 
425 
426 /* Converts a surface to a 32-bit-surface with alpha. The original surface
427    is deleted. */
convertSurface32(SDL_Surface * & s)428 void convertSurface32(SDL_Surface *& s) {
429   /* Silly: The only way I know to create an SDL_PixelFormat is using
430      SDL_CreateRGBSurface; so we need a "sample surface" to get the
431      format from... */
432   SDL_Surface * tmp = SDL_ConvertSurface(s, gSampleSurface32->format, SDL_SWSURFACE);
433   SDLASSERT(tmp);
434   SDL_FreeSurface(s);
435   s = tmp;
436 }
437 
438 
439 /* Return a reference to the pixel at (x, y);
440    assumes that the surface is 32-Bit.
441    NOTE: The surface must be locked before calling this! */
getPixel32(SDL_Surface * surface,int x,int y)442 Uint32 & getPixel32(SDL_Surface *surface, int x, int y) {
443   int bpp = surface->format->BytesPerPixel;
444   return *(Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * bpp);
445 }
446 
447 
448 /* Converts the surface to a format suitable for fast blitting onto the
449    display surface. Contrary to SDL_DisplayFormat, transparency is
450    respected, at least where it is full transparency. Contrary to
451    SDL_DisplayFormatAlpha, it uses ColourKey for paletted surfaces. */
maskedDisplayFormat(SDL_Surface * src)452 SDL_Surface * maskedDisplayFormat(SDL_Surface * src) {
453   SDL_Surface * ret = SDL_ConvertSurface(src,&gMaskedFormat,0);
454   SDLASSERT(ret);
455 
456   if (gMaskedFormat.palette) {
457     /* SDL sieht nicht vor, da� Alpha zu ColourKey konvertiert wird.
458        Seufz. Also selber nachbearbeiten. */
459     SDLASSERT(!SDL_MUSTLOCK(src) && !SDL_MUSTLOCK(ret));
460 
461     ret->flags |= SDL_SRCCOLORKEY;
462     Uint8 colkey = gMaskedFormat.colorkey;
463     ret->format->colorkey = colkey;
464     Uint32 amask = src->format->Amask;
465     Uint8 * srcrow = (Uint8 *) src->pixels;
466     Uint8 * retrow = (Uint8 *) ret->pixels;
467     int w = src->w;
468     int p1 = src->pitch;
469     int p2 = ret->pitch;
470     for (int i=src->h; i; i--, srcrow+=p1, retrow+=p2) {
471       Uint32 * srcpix = (Uint32 *) srcrow;
472       Uint8 * retpix = retrow;
473       for (int j=w; j; j--, srcpix++, retpix++)
474 	if (((*srcpix) & amask)==0)
475 	  *retpix = colkey;
476 	else if ((*retpix)==colkey)
477 	  *retpix = gColkeyErsatz;
478     }
479   }
480   return ret;
481 }
482 
createMaskedDisplaySurface(int w,int h)483 SDL_Surface * createMaskedDisplaySurface(int w, int h) {
484   return SDL_CreateRGBSurface(0,w,h,gMaskedFormat.BitsPerPixel,
485 			      gMaskedFormat.Rmask,gMaskedFormat.Gmask,
486 			      gMaskedFormat.Bmask,gMaskedFormat.Amask);
487 }
488 
489 
490 /* Scales the surface according to our window size;
491    s must be 32 bit.
492    Warning: It is possible that a new surface is created and
493    returned, but is is also possible that just a pointer to s
494    is returned. */
scaleSurface(SDL_Surface * s)495 SDL_Surface * scaleSurface(SDL_Surface * s) {
496 
497   if (!gScale == scale_base)
498     return s;
499 
500   int w2 = s->w * gScale / scale_base;
501   int h2 = s->h * gScale / scale_base;
502   SDL_Surface * ret = SDLTools::createSurface32(w2+1, h2+1);
503 
504   SDL_LockSurface(s);
505   SDL_LockSurface(ret);
506 
507   /* Fill the safety margin (which is there to buffer rounding errors in
508      Area::blitSurface().). */
509   Uint32 trans=SDL_MapRGBA(ret->format,0,0,0,0);
510   SDL_Rect r=rect(w2,0,1,h2+1);
511   SDL_FillRect(ret,&r,trans);
512   r=rect(0,h2,w2,1);
513   SDL_FillRect(ret,&r,trans);
514 
515   for (int y = 0; y < h2; y++) {
516     for (int x = 0; x < w2; x++) {
517       Uint8 * dst = (Uint8 *)ret->pixels + ret->pitch * y + 4 * x;
518       Uint8 * src = (Uint8 *)s->pixels + s->pitch * (y * 4 / gScale) + 4 * (x * 4 / gScale);
519 
520       Uint32 srcpix[16];
521       Uint32 anteil[16];
522       int numpix;
523 
524       /* Scaling is not yet very beautiful; in particular not enlarging */
525       if (gScale >= 4) {
526         numpix = 1;
527         srcpix[0] = * (Uint32*) src;
528         anteil[0] = 16;
529       } else if (gScale == 3) {
530         numpix = 4;
531 	srcpix[0] = * (Uint32*) src;
532 	srcpix[1] = * (Uint32*) (src + 4);
533 	srcpix[2] = * (Uint32*) (src + s->pitch);
534 	srcpix[3] = * (Uint32*) (src + s->pitch + 4);
535 	anteil[0] = (3 - x % 3) * (3 - y % 3);
536 	anteil[1] = (1 + x % 3) * (3 - y % 3);
537 	anteil[2] = (3 - x % 3) * (1 + y % 3);
538 	anteil[3] = (1 + x % 3) * (1 + y % 3);
539       } else if (gScale == 2) {
540         numpix = 4;
541 	srcpix[0] = * (Uint32*) src;
542 	srcpix[1] = * (Uint32*) (src + 4);
543 	srcpix[2] = * (Uint32*) (src + s->pitch);
544 	srcpix[3] = * (Uint32*) (src + s->pitch + 4);
545 	anteil[0] = anteil[1] = anteil[2] = anteil[3] = 4;
546       } else {/* 1 */
547         numpix = 16;
548         for (int j = 0; j < 16; j++) {
549 	  srcpix[j] = * (Uint32*) (src + s->pitch * (j / 4) + 4 * (j % 4));
550 	  anteil[j] = 1;
551 	}
552       }
553       /* Hier ist erstmal Summe der Anteile = 16 */
554 
555       /* Alpha-Kanal einrechnen */
556       Uint32 gesAlpha = 0;
557       for (int j = 0; j < numpix; j++) {
558 	Uint32 sp = srcpix[j];
559 	anteil[j] *= ((Uint8 *) &sp)[3];
560 	gesAlpha += anteil[j];
561       }
562 
563       Uint8 dstpix[4];
564       dstpix[3] = gesAlpha / 16;
565       if (dstpix[3] > 0) {
566 	for (int i = 0; i < 3; i++) {
567           Uint32 komponente = 0;
568 	  for (int j = 0; j < numpix; j++) {
569 	    Uint32 sp = srcpix[j];
570             komponente += anteil[j] * ((Uint8 *) &sp)[i];
571 	  }
572 	  komponente /= gesAlpha;
573 	  dstpix[i] = komponente;
574 	}
575       }
576       memcpy(dst,dstpix,4);
577     }
578   }
579 
580   SDL_UnlockSurface(s);
581   SDL_UnlockSurface(ret);
582 
583   return ret;
584 }
585 
586 
getScale()587 int getScale() {
588   return gScale;
589 }
590 
591 
592 /* Like SDL_PollEvent, but the event is already scaled.
593    And for resize events, the window and scaling is already
594    prepared. */
pollEvent(SDL_Event & evt)595 bool pollEvent(SDL_Event & evt) {
596   if (!SDL_PollEvent(&evt))
597     return false;
598 
599   if (evt.type == SDL_VIDEORESIZE) {
600     setVideoMode(evt.resize.w, evt.resize.h);
601   } else if (evt.type == SDL_MOUSEMOTION) {
602     evt.motion.x -= gShiftX;
603     evt.motion.y -= gShiftY;
604     /* We have to ensure:
605          motion.xy = old-motion.xy + motion.xyrel
606        Without rounding errors. Thus we cannot just
607        multiply motion.xyrel by scale_base/gScale */
608     Uint16 xold, yold;
609     xold = evt.motion.x - evt.motion.xrel;
610     yold = evt.motion.y - evt.motion.yrel;
611     evt.motion.x = evt.motion.x * scale_base / gScale;
612     evt.motion.y = evt.motion.y * scale_base / gScale;
613     xold = xold * scale_base / gScale;
614     yold = yold * scale_base / gScale;
615     evt.motion.xrel = evt.motion.x - xold;
616     evt.motion.yrel = evt.motion.y - yold;
617   } else if (evt.type == SDL_MOUSEBUTTONDOWN ||
618              evt.type == SDL_MOUSEBUTTONUP) {
619     evt.button.x -= gShiftX;
620     evt.button.y -= gShiftY;
621     evt.button.x = evt.button.x * scale_base / gScale;
622     evt.button.y = evt.button.y * scale_base / gScale;
623   }
624 
625   return true;
626 }
627 
628 
629 }  // namespace SDLTools
630 
631 /**************************************************************************/
632 
633 #define max_area_depth 5
634 
635 namespace Area {
636 
637   /* When drawing, three coordinate transformation are done:
638 
639        Area coordinates
640           translate according to the origin of the current Area
641        virtual coordinates
642           scale coordinates
643           Translate to get the black border around the window
644        screen coordinates
645 
646      The two translation cannot really well be merged because one is in
647      virtual pixels and the other one in screen pixels
648 
649      To avoid rounding errors during scaling:
650      The virtual coordinate x corresponds to the screen coordinate
651      floor(x * gScale / scale_base); in screen coordinates, translations
652      aren't exactly linear anymore. So instead of
653          (a) scale(x) + scale(w)
654      always do
655          (b) scale(x+w)
656      There is one exception in which you may use (a): When w is
657      divisible by scale_base; this means that drawing Surfaces or
658      parts of surfaces with size divisible by scale_base is allowed
659   */
660 
661 
662 
663   /* The bounds of each area in the area stack.
664      mBounds[0] is always the whole virtual window.
665      mBounds is in virtual coordinates */
666   SDL_Rect mBounds[max_area_depth];
667   /* mBounds[mActDepth] are the present bounds */
668   int mActDepth;
669 
670   /* I don't use an stl-vector for the updateRects, because:
671      a) I don't want the vector to be resized to a small one
672         each time the updateRects are cleared
673      b) I need direct access to the memory area with the rects
674         to pass them to SDL
675      The mUpdateRects are in screen coordinates
676   */
677   SDL_Rect * mUpdateRects;
678   int mNumUpdateRects;
679   int mReservedUpdateRects;
680   bool mUpdateAll;
681 
682   SDL_Surface * mBackground;
683   SDL_Rect mBackgroundRect; /* screen coordinates */
684   int mBackgroundContext; /* value -1 for "undefined" */
685 
686   /*** Private functions ***/
687 
688 
needMoreUpdateRects()689   void needMoreUpdateRects() {
690     mReservedUpdateRects *= 2;
691     mUpdateRects = (SDL_Rect *) realloc(mUpdateRects,
692                    sizeof(SDL_Rect) * mReservedUpdateRects);
693   }
694 
695 
scale(int & x,int & y)696   void scale(int & x, int & y) {
697     x = x * SDLTools::gScale / scale_base;
698     y = y * SDLTools::gScale / scale_base;
699   }
scale(Sint16 & x,Sint16 & y)700   void scale(Sint16 & x, Sint16 & y) {
701     x = x * SDLTools::gScale / scale_base;
702     y = y * SDLTools::gScale / scale_base;
703   }
704 
705 
706   /* Area coordinates to Screen coordinates */
transformA2S(int & x,int & y)707   void transformA2S(int & x, int & y) {
708     x += mBounds[mActDepth].x;
709     y += mBounds[mActDepth].y;
710     scale(x, y);
711     x += SDLTools::gShiftX;
712     y += SDLTools::gShiftY;
713   }
714 
715 
716   /* Area coordinates to Virtual coordinates */
transformA2V(SDL_Rect & r)717   void transformA2V(SDL_Rect & r) {
718     r.x += mBounds[mActDepth].x;
719     r.y += mBounds[mActDepth].y;
720   }
721 
scale(SDL_Rect & r)722   void scale(SDL_Rect & r) {
723     int right = r.x + r.w;
724     int bot = r.y + r.h;
725     scale(r.x, r.y);
726     scale(right, bot);
727     r.w = right - r.x;
728     r.h = bot - r.y;
729   }
730 
731   /* Virtual coordinates to Screen coordinates */
transformV2S(SDL_Rect & r)732   void transformV2S(SDL_Rect & r) {
733     scale(r);
734     r.x += SDLTools::gShiftX;
735     r.y += SDLTools::gShiftY;
736   }
737 
738 
739   /* Area coordinates to Screen coordinates */
transformA2S(SDL_Rect & r)740   void transformA2S(SDL_Rect & r) {
741     transformA2V(r);
742     transformV2S(r);
743   }
744 
745 
746   /* Clip the (screen) coordinates so that they fit into the window. */
boundS(int & x,int & y)747   void boundS(int & x, int & y) {
748     if (x<0) x=0;
749     if (y<0) y=0;
750     SDL_Surface * screen = SDL_GetVideoSurface();
751     if (x>screen->w) x=screen->w;
752     if (y>screen->h) y=screen->h;
753   }
754 
boundS(SDL_Rect & r)755   void boundS(SDL_Rect & r) {
756     int x0=r.x;
757     int y0=r.y;
758     int x1=r.x+r.w;
759     int y1=r.y+r.h;
760     boundS(x0,y0);
761     boundS(x1,y1);
762     r.x=x0; r.y=y0; r.w=x1-x0; r.h=y1-y0;
763   }
764 
765   /* r is in virtual coordinates */
setClipRectV(SDL_Rect r)766   void setClipRectV(SDL_Rect r) {
767     transformV2S(r);
768     boundS(r);
769     SDL_SetClipRect(SDL_GetVideoSurface(), &r);
770   }
771 
772 
773   /*** Public functions ***/
774 
775 
776   /* This is called from initSDL() */
init()777   void init() {
778     /* 20 updateRects will never be enough, but I prefer that
779        needMoreUpdateRects is called early enough so I notice if
780        there's a bug inside. */
781     mReservedUpdateRects = 20;
782     mUpdateRects = (SDL_Rect *) malloc(sizeof(SDL_Rect) * mReservedUpdateRects);
783     mNumUpdateRects = 0;
784     mUpdateAll = false;
785 
786     mActDepth = 0;
787     /* Only the top-left corner mBounds[0] should be used.
788        Make this rectangle empty, in the hope that this will
789        make it easier to detect bugs when it is falsely used */
790     mBounds[mActDepth] = SDLTools::rect(0, 0, 0, 0);
791     noClip();
792 
793     mBackgroundContext = -1;
794   }
795 
destroy()796   void destroy() {
797     free(mUpdateRects);
798   }
799 
800 
enter(SDL_Rect r)801   void enter(SDL_Rect r) {
802     transformA2V(r);
803 
804     mActDepth++;
805     CASSERT(mActDepth < max_area_depth);
806     if (mActDepth > 1)
807       CASSERT(SDLTools::contained(r, mBounds[mActDepth - 1]));
808     mBounds[mActDepth] = r;
809     noClip();
810   }
811 
leave()812   void leave() {
813     CASSERT(mActDepth > 0);
814     if (mBackgroundContext==mActDepth) mBackgroundContext=-1;
815     mActDepth--;
816     noClip();
817   }
818 
setClip(SDL_Rect r)819   void setClip(SDL_Rect r) {
820     transformA2V(r);
821     if (mActDepth == 0)
822       setClipRectV(r);
823     else {
824       SDL_Rect inter;
825       SDLTools::intersection(r,mBounds[mActDepth],inter);
826       setClipRectV(inter);
827     }
828   }
829 
noClip()830   void noClip() {
831     if (mActDepth == 0) {
832       /* Outer most area is the only one which allows
833          to draw outside */
834       SDL_Surface * s = SDL_GetVideoSurface();
835       SDL_Rect r = SDLTools::rect(0, 0, s->w, s->h);
836       SDL_SetClipRect(s, &r);
837     } else
838       setClipRectV(mBounds[mActDepth]);
839   }
840 
setBackground(const SDL_Rect & r,SDL_Surface * s)841   void setBackground(const SDL_Rect & r, SDL_Surface * s) {
842     mBackground = s;
843     mBackgroundRect = r;
844     transformA2S(mBackgroundRect);
845     mBackgroundContext = mActDepth;
846   }
847 
maskBackground(const Maske * mask,SDL_Rect mr,int x,int y)848   void maskBackground(const Maske * mask, SDL_Rect mr, int x, int y) {
849     if (mBackgroundContext!=-1 && !mask->is_empty()) {
850       int x2 = x + mr.w;
851       int y2 = y + mr.h;
852       transformA2S(x,y);
853       transformA2S(x2,y2);
854       scale(mr.x, mr.y);
855       SDL_Rect dr = SDLTools::rect(x,y,x2-x,y2-y);
856       SDL_Rect ir;
857       if (SDLTools::intersection(mBackgroundRect,dr,ir)) {
858 	boundS(ir);
859 	mr.x += ir.x-dr.x;
860 	mr.y += ir.y-dr.y;
861         mr.w = ir.w;
862         mr.h = ir.h;
863 	mask->masked_blit(mBackground,
864 			  ir.x-mBackgroundRect.x, ir.y-mBackgroundRect.y,
865 			  mr,
866 			  SDL_GetVideoSurface(),ir);
867       }
868     }
869   }
870 
871 
872 
873   /* If the coordinates of srcrect are not a multiple of scale_base, then
874      rounding is done in such a way that the result is correct in the
875      *destination*, not in the source. */
blitSurface(SDL_Surface * src,SDL_Rect srcrect,int dstx,int dsty)876   void blitSurface(SDL_Surface *src, SDL_Rect srcrect, int dstx, int dsty) {
877     int dstx2 = dstx + srcrect.w;
878     int dsty2 = dsty + srcrect.h;
879     transformA2S(dstx, dsty);
880     transformA2S(dstx2, dsty2);
881     SDL_Rect dstrect = SDLTools::rect(dstx, dsty, dstx2-dstx, dsty2-dsty);
882     boundS(dstrect);
883     scale(srcrect.x, srcrect.y);
884     srcrect.x+=dstrect.x-dstx;
885     srcrect.y+=dstrect.y-dsty;
886     srcrect.w = dstrect.w;
887     srcrect.h = dstrect.h;
888 
889     SDL_BlitSurface(src, &srcrect, SDL_GetVideoSurface(), &dstrect);
890   }
891 
blitSurface(SDL_Surface * src,int dstx,int dsty)892   void blitSurface(SDL_Surface *src, int dstx, int dsty) {
893     transformA2S(dstx, dsty);
894     SDL_Rect dstrect = SDLTools::rect(dstx,dsty,src->w,src->h);
895     boundS(dstrect);
896     SDL_Rect srcrect = SDLTools::rect(dstrect.x-dstx,dstrect.y-dsty,
897 				      dstrect.w,dstrect.h);
898     SDL_BlitSurface(src, &srcrect, SDL_GetVideoSurface(), &dstrect);
899   }
900 
901 
fillRect(SDL_Rect dst,const Color & c)902   void fillRect(SDL_Rect dst, const Color & c) {
903     SDL_Surface * s = SDL_GetVideoSurface();
904     transformA2S(dst);
905     boundS(dst);
906     if (((Sint16) dst.w) < 0 || ((Sint16) dst.h) < 0)
907       print_to_stderr("Probably trying to fill rectangle of negative size; this causes an overflow.\n");
908     SDL_FillRect(s, &dst, c.getPixel(s->format));
909 
910   }
911 
fillRect(int x,int y,int w,int h,const Color & c)912   void fillRect(int x, int y, int w, int h, const Color & c) {
913     fillRect(SDLTools::rect(x, y, w, h), c);
914   }
915 
916   /* Fills everything outside the current virtual window */
fillBorder(const Color & c)917   void fillBorder(const Color & c) {
918     SDL_Rect r = SDLTools::rect(0, 0,
919                       SDLTools::gVirtualWidth, SDLTools::gVirtualHeight);
920     transformV2S(r);
921 
922     SDL_Surface * s = SDL_GetVideoSurface();
923     SDL_Rect dst;
924 
925     dst = SDLTools::rect(0, 0, r.x, s->h);
926     boundS(dst);
927     SDL_FillRect(s, &dst, c.getPixel(s->format));
928     dst = SDLTools::rect(r.x + r.w, 0, s->w - (r.x + r.w), s->h);
929     boundS(dst);
930     SDL_FillRect(s, &dst, c.getPixel(s->format));
931     dst = SDLTools::rect(r.x, 0, r.w, r.y);
932     boundS(dst);
933     SDL_FillRect(s, &dst, c.getPixel(s->format));
934     dst = SDLTools::rect(r.x, r.y + r.h, r.w, s->h - (r.y + r.h));
935     boundS(dst);
936     SDL_FillRect(s, &dst, c.getPixel(s->format));
937   }
938 
939 
940   /* You have to call the following methods to make your drawing operations
941      really visible on the screen. (However, the update will take place only
942      at the next call to doUpdate) */
updateRect(SDL_Rect dst)943   void updateRect(SDL_Rect dst) {
944     if (!mUpdateAll) {
945       transformA2V(dst);
946       if (mActDepth != 0) {
947         SDL_Rect inter;
948         if (!SDLTools::intersection(dst,mBounds[mActDepth],inter))
949           return;
950         dst = inter;
951       }
952 
953       transformV2S(dst);
954       boundS(dst);
955       if (mNumUpdateRects >= mReservedUpdateRects)
956 	needMoreUpdateRects();
957       mUpdateRects[mNumUpdateRects++] = dst;
958     }
959   }
960 
961 
updateRect(int x,int y,int w,int h)962   void updateRect(int x, int y, int w, int h) {
963     updateRect(SDLTools::rect(x, y, w, h));
964   }
965 
966   /* Better than calling updateRect(bigRect): Stops collecting small
967      rectangles. All means really all, not only active area. */
updateAll()968   void updateAll() {
969     mUpdateAll = true;
970   }
971 
972 
973 
974   /* To be called only by ui.cpp */
doUpdate()975   void doUpdate() {
976     if (mUpdateAll)
977       SDL_UpdateRect(SDL_GetVideoSurface(), 0, 0, 0, 0);
978     else {
979       //print_to_stderr(_sprintf("%d\n", mNumUpdateRects));
980       SDL_UpdateRects(SDL_GetVideoSurface(), mNumUpdateRects, mUpdateRects);
981     }
982     mUpdateAll = false;
983     mNumUpdateRects = 0;
984   }
985 
986 }
987 
988