fixed bug with losing image filename when scaling bitmaps
[rocksndiamonds.git] / src / libgame / system.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // system.c
10 // ============================================================================
11
12 #include <string.h>
13 #include <signal.h>
14
15 #include "platform.h"
16
17 #include "system.h"
18 #include "image.h"
19 #include "sound.h"
20 #include "setup.h"
21 #include "joystick.h"
22 #include "misc.h"
23
24 #define ENABLE_UNUSED_CODE      0       /* currently unused functions */
25
26
27 /* ========================================================================= */
28 /* exported variables                                                        */
29 /* ========================================================================= */
30
31 struct ProgramInfo      program;
32 struct NetworkInfo      network;
33 struct OptionInfo       options;
34 struct VideoSystemInfo  video;
35 struct AudioSystemInfo  audio;
36 struct GfxInfo          gfx;
37 struct TileCursorInfo   tile_cursor;
38 struct OverlayInfo      overlay;
39 struct ArtworkInfo      artwork;
40 struct JoystickInfo     joystick;
41 struct SetupInfo        setup;
42
43 LevelDirTree           *leveldir_first_all = NULL;
44 LevelDirTree           *leveldir_first = NULL;
45 LevelDirTree           *leveldir_current = NULL;
46 int                     level_nr;
47
48 struct LevelSetInfo     levelset;
49 struct LevelStats       level_stats[MAX_LEVELS];
50
51 DrawWindow             *window = NULL;
52 DrawBuffer             *backbuffer = NULL;
53 DrawBuffer             *drawto = NULL;
54
55 int                     button_status = MB_NOT_PRESSED;
56 boolean                 motion_status = FALSE;
57 int                     wheel_steps = DEFAULT_WHEEL_STEPS;
58 #if defined(TARGET_SDL2)
59 boolean                 keyrepeat_status = TRUE;
60 #endif
61
62 int                     redraw_mask = REDRAW_NONE;
63
64 int                     FrameCounter = 0;
65
66
67 /* ========================================================================= */
68 /* init/close functions                                                      */
69 /* ========================================================================= */
70
71 void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir,
72                      char *program_title, char *icon_title,
73                      char *icon_filename, char *cookie_prefix,
74                      char *program_version_string, int program_version)
75 {
76   program.command_basepath = getBasePath(argv0);
77   program.command_basename = getBaseName(argv0);
78
79   program.config_filename = config_filename;
80
81   program.userdata_subdir = userdata_subdir;
82   program.userdata_path = getUserGameDataDir();
83
84   program.program_title = program_title;
85   program.window_title = "(undefined)";
86   program.icon_title = icon_title;
87
88   program.icon_filename = icon_filename;
89
90   program.cookie_prefix = cookie_prefix;
91
92   program.version_super = VERSION_SUPER(program_version);
93   program.version_major = VERSION_MAJOR(program_version);
94   program.version_minor = VERSION_MINOR(program_version);
95   program.version_patch = VERSION_PATCH(program_version);
96   program.version_ident = program_version;
97
98   program.version_string = program_version_string;
99
100   program.log_filename[LOG_OUT_ID] = getLogFilename(LOG_OUT_BASENAME);
101   program.log_filename[LOG_ERR_ID] = getLogFilename(LOG_ERR_BASENAME);
102   program.log_file[LOG_OUT_ID] = program.log_file_default[LOG_OUT_ID] = stdout;
103   program.log_file[LOG_ERR_ID] = program.log_file_default[LOG_ERR_ID] = stderr;
104
105   program.headless = FALSE;
106 }
107
108 void InitNetworkInfo(boolean enabled, boolean connected, boolean serveronly,
109                      char *server_host, int server_port)
110 {
111   network.enabled = enabled;
112   network.connected = connected;
113   network.serveronly = serveronly;
114
115   network.server_host = server_host;
116   network.server_port = server_port;
117 }
118
119 void InitScoresInfo()
120 {
121   char *global_scores_dir = getPath2(getCommonDataDir(), SCORES_DIRECTORY);
122
123   program.global_scores = directoryExists(global_scores_dir);
124   program.many_scores_per_name = !program.global_scores;
125
126 #if 0
127   if (options.debug)
128   {
129     if (program.global_scores)
130     {
131       Error(ERR_DEBUG, "Using global, multi-user scores directory '%s'.",
132             global_scores_dir);
133       Error(ERR_DEBUG, "Remove to enable single-user scores directory.");
134       Error(ERR_DEBUG, "(This enables multipe score entries per user.)");
135     }
136     else
137     {
138       Error(ERR_DEBUG, "Using private, single-user scores directory.");
139     }
140   }
141 #endif
142
143   free(global_scores_dir);
144 }
145
146 void SetWindowTitle()
147 {
148   program.window_title = program.window_title_function();
149
150   SDLSetWindowTitle();
151 }
152
153 void InitWindowTitleFunction(char *(*window_title_function)(void))
154 {
155   program.window_title_function = window_title_function;
156 }
157
158 void InitExitMessageFunction(void (*exit_message_function)(char *, va_list))
159 {
160   program.exit_message_function = exit_message_function;
161 }
162
163 void InitExitFunction(void (*exit_function)(int))
164 {
165   program.exit_function = exit_function;
166
167   /* set signal handlers to custom exit function */
168   // signal(SIGINT, exit_function);
169   signal(SIGTERM, exit_function);
170
171   /* set exit function to automatically cleanup SDL stuff after exit() */
172   atexit(SDL_Quit);
173 }
174
175 void InitPlatformDependentStuff(void)
176 {
177   // this is initialized in GetOptions(), but may already be used before
178   options.verbose = TRUE;
179
180   OpenLogFiles();
181
182 #if defined(TARGET_SDL2)
183   int sdl_init_flags = SDL_INIT_EVENTS      | SDL_INIT_NOPARACHUTE;
184 #else
185   int sdl_init_flags = SDL_INIT_EVENTTHREAD | SDL_INIT_NOPARACHUTE;
186 #endif
187
188   if (SDL_Init(sdl_init_flags) < 0)
189     Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
190
191   SDLNet_Init();
192 }
193
194 void ClosePlatformDependentStuff(void)
195 {
196   CloseLogFiles();
197 }
198
199 void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize,
200                       int real_sx, int real_sy,
201                       int full_sxsize, int full_sysize,
202                       Bitmap *field_save_buffer)
203 {
204   gfx.sx = sx;
205   gfx.sy = sy;
206   gfx.sxsize = sxsize;
207   gfx.sysize = sysize;
208   gfx.real_sx = real_sx;
209   gfx.real_sy = real_sy;
210   gfx.full_sxsize = full_sxsize;
211   gfx.full_sysize = full_sysize;
212
213   gfx.field_save_buffer = field_save_buffer;
214
215   SetDrawDeactivationMask(REDRAW_NONE);         /* do not deactivate drawing */
216   SetDrawBackgroundMask(REDRAW_NONE);           /* deactivate masked drawing */
217 }
218
219 void InitGfxTileSizeInfo(int game_tile_size, int standard_tile_size)
220 {
221   gfx.game_tile_size = game_tile_size;
222   gfx.standard_tile_size = standard_tile_size;
223 }
224
225 void InitGfxDoor1Info(int dx, int dy, int dxsize, int dysize)
226 {
227   gfx.dx = dx;
228   gfx.dy = dy;
229   gfx.dxsize = dxsize;
230   gfx.dysize = dysize;
231 }
232
233 void InitGfxDoor2Info(int vx, int vy, int vxsize, int vysize)
234 {
235   gfx.vx = vx;
236   gfx.vy = vy;
237   gfx.vxsize = vxsize;
238   gfx.vysize = vysize;
239 }
240
241 void InitGfxDoor3Info(int ex, int ey, int exsize, int eysize)
242 {
243   gfx.ex = ex;
244   gfx.ey = ey;
245   gfx.exsize = exsize;
246   gfx.eysize = eysize;
247 }
248
249 void InitGfxWindowInfo(int win_xsize, int win_ysize)
250 {
251   if (win_xsize != gfx.win_xsize || win_ysize != gfx.win_ysize)
252   {
253     ReCreateBitmap(&gfx.background_bitmap, win_xsize, win_ysize);
254
255 #if defined(TARGET_SDL2)
256     ReCreateBitmap(&gfx.final_screen_bitmap, win_xsize, win_ysize);
257 #endif
258
259     ReCreateBitmap(&gfx.fade_bitmap_backup, win_xsize, win_ysize);
260     ReCreateBitmap(&gfx.fade_bitmap_source, win_xsize, win_ysize);
261     ReCreateBitmap(&gfx.fade_bitmap_target, win_xsize, win_ysize);
262     ReCreateBitmap(&gfx.fade_bitmap_black,  win_xsize, win_ysize);
263
264     ClearRectangle(gfx.fade_bitmap_black, 0, 0, win_xsize, win_ysize);
265   }
266
267   gfx.win_xsize = win_xsize;
268   gfx.win_ysize = win_ysize;
269
270   gfx.background_bitmap_mask = REDRAW_NONE;
271 }
272
273 void InitGfxScrollbufferInfo(int scrollbuffer_width, int scrollbuffer_height)
274 {
275   /* currently only used by MSDOS code to alloc VRAM buffer, if available */
276   /* 2009-03-24: also (temporarily?) used for overlapping blit workaround */
277   gfx.scrollbuffer_width = scrollbuffer_width;
278   gfx.scrollbuffer_height = scrollbuffer_height;
279 }
280
281 void InitGfxClipRegion(boolean enabled, int x, int y, int width, int height)
282 {
283   gfx.clipping_enabled = enabled;
284   gfx.clip_x = x;
285   gfx.clip_y = y;
286   gfx.clip_width = width;
287   gfx.clip_height = height;
288 }
289
290 void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(void))
291 {
292   gfx.draw_busy_anim_function = draw_busy_anim_function;
293 }
294
295 void InitGfxDrawGlobalAnimFunction(void (*draw_global_anim_function)(int, int))
296 {
297   gfx.draw_global_anim_function = draw_global_anim_function;
298 }
299
300 void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int))
301 {
302   gfx.draw_global_border_function = draw_global_border_function;
303 }
304
305 void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int))
306 {
307   gfx.draw_tile_cursor_function = draw_tile_cursor_function;
308 }
309
310 void InitGfxCustomArtworkInfo()
311 {
312   gfx.override_level_graphics = FALSE;
313   gfx.override_level_sounds = FALSE;
314   gfx.override_level_music = FALSE;
315
316   gfx.draw_init_text = TRUE;
317 }
318
319 void InitGfxOtherSettings()
320 {
321   gfx.cursor_mode = CURSOR_DEFAULT;
322 }
323
324 void InitTileCursorInfo()
325 {
326   tile_cursor.enabled = FALSE;
327   tile_cursor.active = FALSE;
328   tile_cursor.moving = FALSE;
329
330   tile_cursor.xpos = 0;
331   tile_cursor.ypos = 0;
332   tile_cursor.x = 0;
333   tile_cursor.y = 0;
334   tile_cursor.target_x = 0;
335   tile_cursor.target_y = 0;
336
337   tile_cursor.sx = 0;
338   tile_cursor.sy = 0;
339 }
340
341 void InitOverlayInfo()
342 {
343   int nr = GRID_ACTIVE_NR();
344   int x, y;
345
346   overlay.enabled = FALSE;
347   overlay.active = FALSE;
348
349   overlay.show_grid = FALSE;
350
351   overlay.grid_xsize = setup.touch.grid_xsize[nr];
352   overlay.grid_ysize = setup.touch.grid_ysize[nr];
353
354   for (x = 0; x < MAX_GRID_XSIZE; x++)
355     for (y = 0; y < MAX_GRID_YSIZE; y++)
356       overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
357
358   overlay.grid_button_highlight = CHAR_GRID_BUTTON_NONE;
359   overlay.grid_button_action = JOY_NO_ACTION;
360
361 #if defined(USE_TOUCH_INPUT_OVERLAY)
362   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
363     overlay.enabled = TRUE;
364 #endif
365 }
366
367 void SetTileCursorEnabled(boolean enabled)
368 {
369   tile_cursor.enabled = enabled;
370 }
371
372 void SetTileCursorActive(boolean active)
373 {
374   tile_cursor.active = active;
375 }
376
377 void SetTileCursorTargetXY(int x, int y)
378 {
379   // delayed placement of tile selection cursor at target position
380   // (tile cursor will be moved to target position step by step)
381
382   tile_cursor.xpos = x;
383   tile_cursor.ypos = y;
384   tile_cursor.target_x = tile_cursor.sx + x * gfx.game_tile_size;
385   tile_cursor.target_y = tile_cursor.sy + y * gfx.game_tile_size;
386
387   tile_cursor.moving = TRUE;
388 }
389
390 void SetTileCursorXY(int x, int y)
391 {
392   // immediate placement of tile selection cursor at target position
393
394   SetTileCursorTargetXY(x, y);
395
396   tile_cursor.x = tile_cursor.target_x;
397   tile_cursor.y = tile_cursor.target_y;
398
399   tile_cursor.moving = FALSE;
400 }
401
402 void SetTileCursorSXSY(int sx, int sy)
403 {
404   tile_cursor.sx = sx;
405   tile_cursor.sy = sy;
406 }
407
408 void SetOverlayEnabled(boolean enabled)
409 {
410   overlay.enabled = enabled;
411 }
412
413 void SetOverlayActive(boolean active)
414 {
415   overlay.active = active;
416 }
417
418 void SetOverlayShowGrid(boolean show_grid)
419 {
420   overlay.show_grid = show_grid;
421
422   SetOverlayActive(show_grid);
423
424   if (show_grid)
425     SetOverlayEnabled(TRUE);
426 }
427
428 boolean GetOverlayActive()
429 {
430   return overlay.active;
431 }
432
433 void SetDrawDeactivationMask(int draw_deactivation_mask)
434 {
435   gfx.draw_deactivation_mask = draw_deactivation_mask;
436 }
437
438 int GetDrawDeactivationMask()
439 {
440   return gfx.draw_deactivation_mask;
441 }
442
443 void SetDrawBackgroundMask(int draw_background_mask)
444 {
445   gfx.draw_background_mask = draw_background_mask;
446 }
447
448 void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask)
449 {
450   if (background_bitmap_tile != NULL)
451     gfx.background_bitmap_mask |= mask;
452   else
453     gfx.background_bitmap_mask &= ~mask;
454
455   if (background_bitmap_tile == NULL)   /* empty background requested */
456     return;
457
458   if (mask == REDRAW_ALL)
459     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
460                     0, 0, video.width, video.height);
461   else if (mask == REDRAW_FIELD)
462     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
463                     gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize);
464   else if (mask == REDRAW_DOOR_1)
465     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
466                     gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize);
467 }
468
469 void SetWindowBackgroundBitmap(Bitmap *background_bitmap_tile)
470 {
471   /* remove every mask before setting mask for window */
472   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
473   SetBackgroundBitmap(NULL, 0xffff);            /* !!! FIX THIS !!! */
474   SetBackgroundBitmap(background_bitmap_tile, REDRAW_ALL);
475 }
476
477 void SetMainBackgroundBitmap(Bitmap *background_bitmap_tile)
478 {
479   /* remove window area mask before setting mask for main area */
480   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
481   SetBackgroundBitmap(NULL, REDRAW_ALL);        /* !!! FIX THIS !!! */
482   SetBackgroundBitmap(background_bitmap_tile, REDRAW_FIELD);
483 }
484
485 void SetDoorBackgroundBitmap(Bitmap *background_bitmap_tile)
486 {
487   /* remove window area mask before setting mask for door area */
488   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
489   SetBackgroundBitmap(NULL, REDRAW_ALL);        /* !!! FIX THIS !!! */
490   SetBackgroundBitmap(background_bitmap_tile, REDRAW_DOOR_1);
491 }
492
493
494 /* ========================================================================= */
495 /* video functions                                                           */
496 /* ========================================================================= */
497
498 inline static int GetRealDepth(int depth)
499 {
500   return (depth == DEFAULT_DEPTH ? video.default_depth : depth);
501 }
502
503 inline static void sysFillRectangle(Bitmap *bitmap, int x, int y,
504                                     int width, int height, Pixel color)
505 {
506   SDLFillRectangle(bitmap, x, y, width, height, color);
507
508   if (bitmap == backbuffer)
509     SetRedrawMaskFromArea(x, y, width, height);
510 }
511
512 inline static void sysCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
513                                int src_x, int src_y, int width, int height,
514                                int dst_x, int dst_y, int mask_mode)
515 {
516   SDLCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
517               dst_x, dst_y, mask_mode);
518
519   if (dst_bitmap == backbuffer)
520     SetRedrawMaskFromArea(dst_x, dst_y, width, height);
521 }
522
523 void LimitScreenUpdates(boolean enable)
524 {
525   SDLLimitScreenUpdates(enable);
526 }
527
528 void InitVideoDefaults(void)
529 {
530   video.default_depth = 32;
531 }
532
533 void InitVideoDisplay(void)
534 {
535   if (program.headless)
536     return;
537
538   SDLInitVideoDisplay();
539 #if defined(TARGET_SDL2)
540   SDLSetDisplaySize();
541 #endif
542 }
543
544 void CloseVideoDisplay(void)
545 {
546   KeyboardAutoRepeatOn();
547
548   SDL_QuitSubSystem(SDL_INIT_VIDEO);
549 }
550
551 void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
552 {
553   video.width = width;
554   video.height = height;
555   video.depth = GetRealDepth(depth);
556
557   video.screen_width = width;
558   video.screen_height = height;
559   video.screen_xoffset = 0;
560   video.screen_yoffset = 0;
561
562   video.fullscreen_available = FULLSCREEN_STATUS;
563   video.fullscreen_enabled = FALSE;
564
565   video.window_scaling_available = WINDOW_SCALING_STATUS;
566
567   video.frame_delay = 0;
568   video.frame_delay_value = GAME_FRAME_DELAY;
569
570   video.shifted_up = FALSE;
571   video.shifted_up_pos = 0;
572   video.shifted_up_pos_last = 0;
573   video.shifted_up_delay = 0;
574   video.shifted_up_delay_value = ONE_SECOND_DELAY / 4;
575
576   SDLInitVideoBuffer(fullscreen);
577
578   video.initialized = !program.headless;
579
580   drawto = backbuffer;
581 }
582
583 inline static void FreeBitmapPointers(Bitmap *bitmap)
584 {
585   if (bitmap == NULL)
586     return;
587
588   SDLFreeBitmapPointers(bitmap);
589
590   checked_free(bitmap->source_filename);
591   bitmap->source_filename = NULL;
592 }
593
594 inline static void TransferBitmapPointers(Bitmap *src_bitmap,
595                                           Bitmap *dst_bitmap)
596 {
597   if (src_bitmap == NULL || dst_bitmap == NULL)
598     return;
599
600   FreeBitmapPointers(dst_bitmap);
601
602   *dst_bitmap = *src_bitmap;
603 }
604
605 void FreeBitmap(Bitmap *bitmap)
606 {
607   if (bitmap == NULL)
608     return;
609
610   FreeBitmapPointers(bitmap);
611
612   free(bitmap);
613 }
614
615 Bitmap *CreateBitmapStruct(void)
616 {
617   return checked_calloc(sizeof(Bitmap));
618 }
619
620 Bitmap *CreateBitmap(int width, int height, int depth)
621 {
622   Bitmap *new_bitmap = CreateBitmapStruct();
623   int real_width  = MAX(1, width);      /* prevent zero bitmap width */
624   int real_height = MAX(1, height);     /* prevent zero bitmap height */
625   int real_depth  = GetRealDepth(depth);
626
627   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
628
629   new_bitmap->width  = real_width;
630   new_bitmap->height = real_height;
631
632   return new_bitmap;
633 }
634
635 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
636 {
637   if (*bitmap != NULL)
638   {
639     /* if new bitmap size fits into old one, no need to re-create it */
640     if (width  <= (*bitmap)->width &&
641         height <= (*bitmap)->height)
642       return;
643
644     /* else adjust size so that old and new bitmap size fit into it */
645     width  = MAX(width,  (*bitmap)->width);
646     height = MAX(height, (*bitmap)->height);
647   }
648
649   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
650
651   if (*bitmap == NULL)
652   {
653     *bitmap = new_bitmap;
654   }
655   else
656   {
657     TransferBitmapPointers(new_bitmap, *bitmap);
658     free(new_bitmap);
659   }
660 }
661
662 void CloseWindow(DrawWindow *window)
663 {
664 }
665
666 void SetRedrawMaskFromArea(int x, int y, int width, int height)
667 {
668   int x1 = x;
669   int y1 = y;
670   int x2 = x + width - 1;
671   int y2 = y + height - 1;
672
673   if (width == 0 || height == 0)
674     return;
675
676   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
677     redraw_mask |= REDRAW_FIELD;
678   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
679     redraw_mask |= REDRAW_DOOR_1;
680   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
681     redraw_mask |= REDRAW_DOOR_2;
682   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
683     redraw_mask |= REDRAW_DOOR_3;
684   else
685     redraw_mask = REDRAW_ALL;
686 }
687
688 inline static boolean CheckDrawingArea(int x, int y, int width, int height,
689                                        int draw_mask)
690 {
691   if (draw_mask == REDRAW_NONE)
692     return FALSE;
693
694   if (draw_mask & REDRAW_ALL)
695     return TRUE;
696
697   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
698     return TRUE;
699
700   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
701     return TRUE;
702
703   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
704     return TRUE;
705
706   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
707     return TRUE;
708
709   return FALSE;
710 }
711
712 boolean DrawingDeactivatedField()
713 {
714   if (program.headless)
715     return TRUE;
716
717   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
718     return TRUE;
719
720   return FALSE;
721 }
722
723 boolean DrawingDeactivated(int x, int y, int width, int height)
724 {
725   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
726 }
727
728 boolean DrawingOnBackground(int x, int y)
729 {
730   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
731           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
732 }
733
734 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
735                                   int *width, int *height, boolean is_dest)
736 {
737   int clip_x, clip_y, clip_width, clip_height;
738
739   if (gfx.clipping_enabled && is_dest)  /* only clip destination bitmap */
740   {
741     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
742     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
743     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
744     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
745   }
746   else
747   {
748     clip_x = 0;
749     clip_y = 0;
750     clip_width = bitmap->width;
751     clip_height = bitmap->height;
752   }
753
754   /* skip if rectangle completely outside bitmap */
755
756   if (*x + *width  <= clip_x ||
757       *y + *height <= clip_y ||
758       *x >= clip_x + clip_width ||
759       *y >= clip_y + clip_height)
760     return FALSE;
761
762   /* clip if rectangle overlaps bitmap */
763
764   if (*x < clip_x)
765   {
766     *width -= clip_x - *x;
767     *x = clip_x;
768   }
769   else if (*x + *width > clip_x + clip_width)
770   {
771     *width = clip_x + clip_width - *x;
772   }
773
774   if (*y < clip_y)
775   {
776     *height -= clip_y - *y;
777     *y = clip_y;
778   }
779   else if (*y + *height > clip_y + clip_height)
780   {
781     *height = clip_y + clip_height - *y;
782   }
783
784   return TRUE;
785 }
786
787 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
788                 int src_x, int src_y, int width, int height,
789                 int dst_x, int dst_y)
790 {
791   int dst_x_unclipped = dst_x;
792   int dst_y_unclipped = dst_y;
793
794   if (program.headless)
795     return;
796
797   if (src_bitmap == NULL || dst_bitmap == NULL)
798     return;
799
800   if (DrawingDeactivated(dst_x, dst_y, width, height))
801     return;
802
803   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
804       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
805     return;
806
807   /* source x/y might need adjustment if destination x/y was clipped top/left */
808   src_x += dst_x - dst_x_unclipped;
809   src_y += dst_y - dst_y_unclipped;
810
811 #if defined(TARGET_SDL2)
812   /* !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!! */
813   /* !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!! */
814   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
815      but is already fixed in SVN and should therefore finally be fixed with
816      the next official SDL release, which is probably version 1.2.14.) */
817   /* !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!! */
818
819   if (src_bitmap == dst_bitmap)
820   {
821     /* needed when blitting directly to same bitmap -- should not be needed with
822        recent SDL libraries, but apparently does not work in 1.2.11 directly */
823
824     static Bitmap *tmp_bitmap = NULL;
825     static int tmp_bitmap_xsize = 0;
826     static int tmp_bitmap_ysize = 0;
827
828     /* start with largest static bitmaps for initial bitmap size ... */
829     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
830     {
831       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
832       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
833     }
834
835     /* ... and allow for later re-adjustments due to custom artwork bitmaps */
836     if (src_bitmap->width > tmp_bitmap_xsize ||
837         src_bitmap->height > tmp_bitmap_ysize)
838     {
839       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
840       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
841
842       FreeBitmap(tmp_bitmap);
843
844       tmp_bitmap = NULL;
845     }
846
847     if (tmp_bitmap == NULL)
848       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
849                                 DEFAULT_DEPTH);
850
851     sysCopyArea(src_bitmap, tmp_bitmap,
852                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
853     sysCopyArea(tmp_bitmap, dst_bitmap,
854                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
855
856     return;
857   }
858 #endif
859
860   sysCopyArea(src_bitmap, dst_bitmap,
861               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
862 }
863
864 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
865                      int src_x, int src_y, int src_width, int src_height,
866                      int dst_x, int dst_y, int dst_width, int dst_height)
867 {
868   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
869   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
870   int dst_xsize = dst_width;
871   int dst_ysize = dst_height;
872   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
873   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
874   int x, y;
875
876   for (y = 0; y < src_ysteps; y++)
877   {
878     for (x = 0; x < src_xsteps; x++)
879     {
880       int draw_x = dst_x + x * src_xsize;
881       int draw_y = dst_y + y * src_ysize;
882       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
883       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
884
885       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
886                  draw_x, draw_y);
887     }
888   }
889 }
890
891 void FadeRectangle(int x, int y, int width, int height,
892                    int fade_mode, int fade_delay, int post_delay,
893                    void (*draw_border_function)(void))
894 {
895   /* (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined) */
896   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
897     return;
898
899   SDLFadeRectangle(x, y, width, height,
900                    fade_mode, fade_delay, post_delay, draw_border_function);
901 }
902
903 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
904                    Pixel color)
905 {
906   if (DrawingDeactivated(x, y, width, height))
907     return;
908
909   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
910     return;
911
912   sysFillRectangle(bitmap, x, y, width, height, color);
913 }
914
915 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
916 {
917   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
918 }
919
920 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
921                                 int width, int height)
922 {
923   if (DrawingOnBackground(x, y))
924     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
925   else
926     ClearRectangle(bitmap, x, y, width, height);
927 }
928
929 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
930                       int src_x, int src_y, int width, int height,
931                       int dst_x, int dst_y)
932 {
933   if (DrawingDeactivated(dst_x, dst_y, width, height))
934     return;
935
936   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
937               dst_x, dst_y, BLIT_MASKED);
938 }
939
940 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
941                             int src_x, int src_y, int width, int height,
942                             int dst_x, int dst_y)
943 {
944   if (DrawingOnBackground(dst_x, dst_y))
945   {
946     /* draw background */
947     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
948                dst_x, dst_y);
949
950     /* draw foreground */
951     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
952                      dst_x, dst_y);
953   }
954   else
955     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
956                dst_x, dst_y);
957 }
958
959 void BlitTexture(Bitmap *bitmap,
960                 int src_x, int src_y, int width, int height,
961                 int dst_x, int dst_y)
962 {
963   if (bitmap == NULL)
964     return;
965
966   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
967                  BLIT_OPAQUE);
968 }
969
970 void BlitTextureMasked(Bitmap *bitmap,
971                        int src_x, int src_y, int width, int height,
972                        int dst_x, int dst_y)
973 {
974   if (bitmap == NULL)
975     return;
976
977   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
978                  BLIT_MASKED);
979 }
980
981 void BlitToScreen(Bitmap *bitmap,
982                   int src_x, int src_y, int width, int height,
983                   int dst_x, int dst_y)
984 {
985   if (bitmap == NULL)
986     return;
987
988   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
989     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
990                width, height, dst_x, dst_y);
991   else
992     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
993 }
994
995 void BlitToScreenMasked(Bitmap *bitmap,
996                         int src_x, int src_y, int width, int height,
997                         int dst_x, int dst_y)
998 {
999   if (bitmap == NULL)
1000     return;
1001
1002   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1003     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1004                      width, height, dst_x, dst_y);
1005   else
1006     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1007 }
1008
1009 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
1010                          int to_x, int to_y)
1011 {
1012   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
1013 }
1014
1015 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1016                          int to_x, int to_y)
1017 {
1018   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1019 }
1020
1021 void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1022               int to_x, int to_y, Pixel pixel, int line_width)
1023 {
1024   int x, y;
1025
1026   if (program.headless)
1027     return;
1028
1029   for (x = 0; x < line_width; x++)
1030   {
1031     for (y = 0; y < line_width; y++)
1032     {
1033       int dx = x - line_width / 2;
1034       int dy = y - line_width / 2;
1035
1036       if ((x == 0 && y == 0) ||
1037           (x == 0 && y == line_width - 1) ||
1038           (x == line_width - 1 && y == 0) ||
1039           (x == line_width - 1 && y == line_width - 1))
1040         continue;
1041
1042       SDLDrawLine(bitmap,
1043                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1044     }
1045   }
1046 }
1047
1048 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1049 {
1050   int line_width = 4;
1051   int i;
1052
1053   for (i = 0; i < num_points - 1; i++)
1054     DrawLine(bitmap, points[i].x, points[i].y,
1055              points[i + 1].x, points[i + 1].y, pixel, line_width);
1056
1057   /*
1058   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1059   */
1060 }
1061
1062 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1063 {
1064   if (program.headless)
1065     return BLACK_PIXEL;
1066
1067   if (x < 0 || x >= bitmap->width ||
1068       y < 0 || y >= bitmap->height)
1069     return BLACK_PIXEL;
1070
1071   return SDLGetPixel(bitmap, x, y);
1072 }
1073
1074 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1075                       unsigned int color_g, unsigned int color_b)
1076 {
1077   if (program.headless)
1078     return BLACK_PIXEL;
1079
1080   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1081 }
1082
1083 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
1084 {
1085   unsigned int color_r = (color >> 16) & 0xff;
1086   unsigned int color_g = (color >>  8) & 0xff;
1087   unsigned int color_b = (color >>  0) & 0xff;
1088
1089   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
1090 }
1091
1092 void KeyboardAutoRepeatOn(void)
1093 {
1094 #if defined(TARGET_SDL2)
1095   keyrepeat_status = TRUE;
1096 #else
1097   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2,
1098                       SDL_DEFAULT_REPEAT_INTERVAL / 2);
1099   SDL_EnableUNICODE(1);
1100 #endif
1101 }
1102
1103 void KeyboardAutoRepeatOff(void)
1104 {
1105 #if defined(TARGET_SDL2)
1106   keyrepeat_status = FALSE;
1107 #else
1108   SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
1109   SDL_EnableUNICODE(0);
1110 #endif
1111 }
1112
1113 boolean SetVideoMode(boolean fullscreen)
1114 {
1115   return SDLSetVideoMode(fullscreen);
1116 }
1117
1118 void SetVideoFrameDelay(unsigned int frame_delay_value)
1119 {
1120   video.frame_delay_value = frame_delay_value;
1121 }
1122
1123 unsigned int GetVideoFrameDelay()
1124 {
1125   return video.frame_delay_value;
1126 }
1127
1128 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1129 {
1130   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1131       (!fullscreen && video.fullscreen_enabled))
1132     fullscreen = SetVideoMode(fullscreen);
1133
1134   return fullscreen;
1135 }
1136
1137 Bitmap *LoadImage(char *filename)
1138 {
1139   Bitmap *new_bitmap;
1140
1141   new_bitmap = SDLLoadImage(filename);
1142
1143   if (new_bitmap)
1144     new_bitmap->source_filename = getStringCopy(filename);
1145
1146   return new_bitmap;
1147 }
1148
1149 Bitmap *LoadCustomImage(char *basename)
1150 {
1151   char *filename = getCustomImageFilename(basename);
1152   Bitmap *new_bitmap;
1153
1154   if (filename == NULL)
1155     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1156
1157   if ((new_bitmap = LoadImage(filename)) == NULL)
1158     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1159
1160   return new_bitmap;
1161 }
1162
1163 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1164 {
1165   char *filename = getCustomImageFilename(basename);
1166   Bitmap *new_bitmap;
1167
1168   if (filename == NULL)         /* (should never happen) */
1169   {
1170     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1171     return;
1172   }
1173
1174   if (strEqual(filename, bitmap->source_filename))
1175   {
1176     /* The old and new image are the same (have the same filename and path).
1177        This usually means that this image does not exist in this graphic set
1178        and a fallback to the existing image is done. */
1179
1180     return;
1181   }
1182
1183   if ((new_bitmap = LoadImage(filename)) == NULL)
1184   {
1185     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1186     return;
1187   }
1188
1189   if (bitmap->width != new_bitmap->width ||
1190       bitmap->height != new_bitmap->height)
1191   {
1192     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1193           filename);
1194     FreeBitmap(new_bitmap);
1195     return;
1196   }
1197
1198   TransferBitmapPointers(new_bitmap, bitmap);
1199   free(new_bitmap);
1200 }
1201
1202 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1203 {
1204   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1205 }
1206
1207 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1208 {
1209   if (bitmaps[IMG_BITMAP_CUSTOM])
1210   {
1211     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1212
1213     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1214   }
1215
1216   if (gfx.game_tile_size == gfx.standard_tile_size)
1217   {
1218     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1219
1220     return;
1221   }
1222
1223   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1224   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1225   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1226
1227   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1228
1229   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1230   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1231 }
1232
1233 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1234                                 int tile_size, boolean create_small_bitmaps)
1235 {
1236   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1237   Bitmap *tmp_bitmap_final = NULL;
1238   Bitmap *tmp_bitmap_0 = NULL;
1239   Bitmap *tmp_bitmap_1 = NULL;
1240   Bitmap *tmp_bitmap_2 = NULL;
1241   Bitmap *tmp_bitmap_4 = NULL;
1242   Bitmap *tmp_bitmap_8 = NULL;
1243   Bitmap *tmp_bitmap_16 = NULL;
1244   Bitmap *tmp_bitmap_32 = NULL;
1245   int width_final, height_final;
1246   int width_0, height_0;
1247   int width_1, height_1;
1248   int width_2, height_2;
1249   int width_4, height_4;
1250   int width_8, height_8;
1251   int width_16, height_16;
1252   int width_32, height_32;
1253   int old_width, old_height;
1254   int i;
1255
1256   print_timestamp_init("CreateScaledBitmaps");
1257
1258   old_width  = old_bitmap->width;
1259   old_height = old_bitmap->height;
1260
1261   /* calculate new image dimensions for final image size */
1262   width_final  = old_width  * zoom_factor;
1263   height_final = old_height * zoom_factor;
1264
1265   /* get image with final size (this might require scaling up) */
1266   /* ("final" size may result in non-standard tile size image) */
1267   if (zoom_factor != 1)
1268     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1269   else
1270     tmp_bitmap_final = old_bitmap;
1271
1272   UPDATE_BUSY_STATE();
1273
1274   width_0  = width_1  = width_final;
1275   height_0 = height_1 = height_final;
1276
1277   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1278
1279   if (create_small_bitmaps)
1280   {
1281     /* check if we have a non-gameplay tile size image */
1282     if (tile_size != gfx.game_tile_size)
1283     {
1284       /* get image with gameplay tile size */
1285       width_0  = width_final  * gfx.game_tile_size / tile_size;
1286       height_0 = height_final * gfx.game_tile_size / tile_size;
1287
1288       if (width_0 == old_width)
1289         tmp_bitmap_0 = old_bitmap;
1290       else if (width_0 == width_final)
1291         tmp_bitmap_0 = tmp_bitmap_final;
1292       else
1293         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1294
1295       UPDATE_BUSY_STATE();
1296     }
1297
1298     /* check if we have a non-standard tile size image */
1299     if (tile_size != gfx.standard_tile_size)
1300     {
1301       /* get image with standard tile size */
1302       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1303       height_1 = height_final * gfx.standard_tile_size / tile_size;
1304
1305       if (width_1 == old_width)
1306         tmp_bitmap_1 = old_bitmap;
1307       else if (width_1 == width_final)
1308         tmp_bitmap_1 = tmp_bitmap_final;
1309       else if (width_1 == width_0)
1310         tmp_bitmap_1 = tmp_bitmap_0;
1311       else
1312         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1313
1314       UPDATE_BUSY_STATE();
1315     }
1316
1317     /* calculate new image dimensions for small images */
1318     width_2  = width_1  / 2;
1319     height_2 = height_1 / 2;
1320     width_4  = width_1  / 4;
1321     height_4 = height_1 / 4;
1322     width_8  = width_1  / 8;
1323     height_8 = height_1 / 8;
1324     width_16  = width_1  / 16;
1325     height_16 = height_1 / 16;
1326     width_32  = width_1  / 32;
1327     height_32 = height_1 / 32;
1328
1329     /* get image with 1/2 of normal size (for use in the level editor) */
1330     if (width_2 == old_width)
1331       tmp_bitmap_2 = old_bitmap;
1332     else
1333       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1334
1335     UPDATE_BUSY_STATE();
1336
1337     /* get image with 1/4 of normal size (for use in the level editor) */
1338     if (width_4 == old_width)
1339       tmp_bitmap_4 = old_bitmap;
1340     else
1341       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1342
1343     UPDATE_BUSY_STATE();
1344
1345     /* get image with 1/8 of normal size (for use on the preview screen) */
1346     if (width_8 == old_width)
1347       tmp_bitmap_8 = old_bitmap;
1348     else
1349       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1350
1351     UPDATE_BUSY_STATE();
1352
1353     /* get image with 1/16 of normal size (for use on the preview screen) */
1354     if (width_16 == old_width)
1355       tmp_bitmap_16 = old_bitmap;
1356     else
1357       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1358
1359     UPDATE_BUSY_STATE();
1360
1361     /* get image with 1/32 of normal size (for use on the preview screen) */
1362     if (width_32 == old_width)
1363       tmp_bitmap_32 = old_bitmap;
1364     else
1365       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1366
1367     UPDATE_BUSY_STATE();
1368
1369     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1370     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1371     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1372     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1373     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1374     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1375
1376     if (width_0 != width_1)
1377       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1378
1379     if (bitmaps[IMG_BITMAP_CUSTOM])
1380       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1381     else
1382       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1383
1384     boolean free_old_bitmap = TRUE;
1385
1386     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1387       if (bitmaps[i] == old_bitmap)
1388         free_old_bitmap = FALSE;
1389
1390     if (free_old_bitmap)
1391     {
1392       /* copy image filename from old to new standard sized bitmap */
1393       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1394         getStringCopy(old_bitmap->source_filename);
1395
1396       FreeBitmap(old_bitmap);
1397     }
1398   }
1399   else
1400   {
1401     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1402   }
1403
1404   UPDATE_BUSY_STATE();
1405
1406   print_timestamp_done("CreateScaledBitmaps");
1407 }
1408
1409 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1410                                   int tile_size)
1411 {
1412   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1413 }
1414
1415 void CreateBitmapTextures(Bitmap **bitmaps)
1416 {
1417   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1418 }
1419
1420 void FreeBitmapTextures(Bitmap **bitmaps)
1421 {
1422   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1423 }
1424
1425 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1426 {
1427   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1428 }
1429
1430
1431 /* ------------------------------------------------------------------------- */
1432 /* mouse pointer functions                                                   */
1433 /* ------------------------------------------------------------------------- */
1434
1435 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1436
1437 /* XPM image definitions */
1438 static const char *cursor_image_none[] =
1439 {
1440   /* width height num_colors chars_per_pixel */
1441   "    16    16        3            1",
1442
1443   /* colors */
1444   "X c #000000",
1445   ". c #ffffff",
1446   "  c None",
1447
1448   /* pixels */
1449   "                ",
1450   "                ",
1451   "                ",
1452   "                ",
1453   "                ",
1454   "                ",
1455   "                ",
1456   "                ",
1457   "                ",
1458   "                ",
1459   "                ",
1460   "                ",
1461   "                ",
1462   "                ",
1463   "                ",
1464   "                ",
1465
1466   /* hot spot */
1467   "0,0"
1468 };
1469
1470 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1471 static const char *cursor_image_dot[] =
1472 {
1473   /* width height num_colors chars_per_pixel */
1474   "    16    16        3            1",
1475
1476   /* colors */
1477   "X c #000000",
1478   ". c #ffffff",
1479   "  c None",
1480
1481   /* pixels */
1482   " X              ",
1483   "X.X             ",
1484   " X              ",
1485   "                ",
1486   "                ",
1487   "                ",
1488   "                ",
1489   "                ",
1490   "                ",
1491   "                ",
1492   "                ",
1493   "                ",
1494   "                ",
1495   "                ",
1496   "                ",
1497   "                ",
1498
1499   /* hot spot */
1500   "1,1"
1501 };
1502 static const char **cursor_image_playfield = cursor_image_dot;
1503 #else
1504 /* some people complained about a "white dot" on the screen and thought it
1505    was a graphical error... OK, let's just remove the whole pointer :-) */
1506 static const char **cursor_image_playfield = cursor_image_none;
1507 #endif
1508
1509 static const int cursor_bit_order = BIT_ORDER_MSB;
1510
1511 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1512 {
1513   struct MouseCursorInfo *cursor;
1514   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1515   int header_lines = 4;
1516   int x, y, i;
1517
1518   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1519
1520   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1521
1522   i = -1;
1523   for (y = 0; y < cursor->width; y++)
1524   {
1525     for (x = 0; x < cursor->height; x++)
1526     {
1527       int bit_nr = x % 8;
1528       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1529
1530       if (bit_nr == 0)
1531       {
1532         i++;
1533         cursor->data[i] = cursor->mask[i] = 0;
1534       }
1535
1536       switch (image[header_lines + y][x])
1537       {
1538         case 'X':
1539           cursor->data[i] |= bit_mask;
1540           cursor->mask[i] |= bit_mask;
1541           break;
1542
1543         case '.':
1544           cursor->mask[i] |= bit_mask;
1545           break;
1546
1547         case ' ':
1548           break;
1549       }
1550     }
1551   }
1552
1553   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1554
1555   return cursor;
1556 }
1557
1558 void SetMouseCursor(int mode)
1559 {
1560   static struct MouseCursorInfo *cursor_none = NULL;
1561   static struct MouseCursorInfo *cursor_playfield = NULL;
1562   struct MouseCursorInfo *cursor_new;
1563
1564   if (cursor_none == NULL)
1565     cursor_none = get_cursor_from_image(cursor_image_none);
1566
1567   if (cursor_playfield == NULL)
1568     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1569
1570   cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
1571                 mode == CURSOR_NONE      ? cursor_none :
1572                 mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1573
1574   SDLSetMouseCursor(cursor_new);
1575
1576   gfx.cursor_mode = mode;
1577 }
1578
1579
1580 /* ========================================================================= */
1581 /* audio functions                                                           */
1582 /* ========================================================================= */
1583
1584 void OpenAudio(void)
1585 {
1586   /* always start with reliable default values */
1587   audio.sound_available = FALSE;
1588   audio.music_available = FALSE;
1589   audio.loops_available = FALSE;
1590
1591   audio.sound_enabled = FALSE;
1592   audio.sound_deactivated = FALSE;
1593
1594   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1595   audio.mixer_pid = 0;
1596   audio.device_name = NULL;
1597   audio.device_fd = -1;
1598
1599   audio.num_channels = 0;
1600   audio.music_channel = 0;
1601   audio.first_sound_channel = 0;
1602
1603   SDLOpenAudio();
1604 }
1605
1606 void CloseAudio(void)
1607 {
1608   SDLCloseAudio();
1609
1610   audio.sound_enabled = FALSE;
1611 }
1612
1613 void SetAudioMode(boolean enabled)
1614 {
1615   if (!audio.sound_available)
1616     return;
1617
1618   audio.sound_enabled = enabled;
1619 }
1620
1621
1622 /* ========================================================================= */
1623 /* event functions                                                           */
1624 /* ========================================================================= */
1625
1626 boolean PendingEvent(void)
1627 {
1628   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1629 }
1630
1631 void WaitEvent(Event *event)
1632 {
1633   SDLWaitEvent(event);
1634 }
1635
1636 void PeekEvent(Event *event)
1637 {
1638 #if defined(TARGET_SDL2)
1639   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1640 #else
1641   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
1642 #endif
1643 }
1644
1645 void CheckQuitEvent(void)
1646 {
1647   if (SDL_QuitRequested())
1648     program.exit_function(0);
1649 }
1650
1651 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1652 {
1653 #if defined(TARGET_SDL2)
1654   /* key up/down events in SDL2 do not return text characters anymore */
1655   return event->keysym.sym;
1656 #else
1657
1658 #if ENABLE_UNUSED_CODE
1659   printf("unicode == '%d', sym == '%d', mod == '0x%04x'\n",
1660          (int)event->keysym.unicode,
1661          (int)event->keysym.sym,
1662          (int)SDL_GetModState());
1663 #endif
1664
1665   if (with_modifiers &&
1666       event->keysym.unicode > 0x0000 &&
1667       event->keysym.unicode < 0x2000)
1668     return event->keysym.unicode;
1669   else
1670     return event->keysym.sym;
1671
1672 #endif
1673 }
1674
1675 KeyMod HandleKeyModState(Key key, int key_status)
1676 {
1677   static KeyMod current_modifiers = KMOD_None;
1678
1679   if (key != KSYM_UNDEFINED)    /* new key => check for modifier key change */
1680   {
1681     KeyMod new_modifier = KMOD_None;
1682
1683     switch(key)
1684     {
1685       case KSYM_Shift_L:
1686         new_modifier = KMOD_Shift_L;
1687         break;
1688       case KSYM_Shift_R:
1689         new_modifier = KMOD_Shift_R;
1690         break;
1691       case KSYM_Control_L:
1692         new_modifier = KMOD_Control_L;
1693         break;
1694       case KSYM_Control_R:
1695         new_modifier = KMOD_Control_R;
1696         break;
1697       case KSYM_Meta_L:
1698         new_modifier = KMOD_Meta_L;
1699         break;
1700       case KSYM_Meta_R:
1701         new_modifier = KMOD_Meta_R;
1702         break;
1703       case KSYM_Alt_L:
1704         new_modifier = KMOD_Alt_L;
1705         break;
1706       case KSYM_Alt_R:
1707         new_modifier = KMOD_Alt_R;
1708         break;
1709       default:
1710         break;
1711     }
1712
1713     if (key_status == KEY_PRESSED)
1714       current_modifiers |= new_modifier;
1715     else
1716       current_modifiers &= ~new_modifier;
1717   }
1718
1719   return current_modifiers;
1720 }
1721
1722 KeyMod GetKeyModState()
1723 {
1724   return (KeyMod)SDL_GetModState();
1725 }
1726
1727 KeyMod GetKeyModStateFromEvents()
1728 {
1729   /* always use key modifier state as tracked from key events (this is needed
1730      if the modifier key event was injected into the event queue, but the key
1731      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1732      query the keys as held pressed on the keyboard) -- this case is currently
1733      only used to filter out clipboard insert events from "True X-Mouse" tool */
1734
1735   return HandleKeyModState(KSYM_UNDEFINED, 0);
1736 }
1737
1738 void StartTextInput(int x, int y, int width, int height)
1739 {
1740 #if defined(TARGET_SDL2)
1741 #if defined(HAS_SCREEN_KEYBOARD)
1742   SDL_StartTextInput();
1743
1744   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1745   {
1746     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1747     video.shifted_up_delay = SDL_GetTicks();
1748     video.shifted_up = TRUE;
1749   }
1750 #endif
1751 #endif
1752 }
1753
1754 void StopTextInput()
1755 {
1756 #if defined(TARGET_SDL2)
1757 #if defined(HAS_SCREEN_KEYBOARD)
1758   SDL_StopTextInput();
1759
1760   if (video.shifted_up)
1761   {
1762     video.shifted_up_pos = 0;
1763     video.shifted_up_delay = SDL_GetTicks();
1764     video.shifted_up = FALSE;
1765   }
1766 #endif
1767 #endif
1768 }
1769
1770 boolean CheckCloseWindowEvent(ClientMessageEvent *event)
1771 {
1772   if (event->type != EVENT_CLIENTMESSAGE)
1773     return FALSE;
1774
1775   return TRUE;          /* the only possible message here is SDL_QUIT */
1776 }
1777
1778
1779 /* ========================================================================= */
1780 /* joystick functions                                                        */
1781 /* ========================================================================= */
1782
1783 void InitJoysticks()
1784 {
1785   int i;
1786
1787 #if defined(NO_JOYSTICK)
1788   return;       /* joysticks generally deactivated by compile-time directive */
1789 #endif
1790
1791   /* always start with reliable default values */
1792   joystick.status = JOYSTICK_NOT_AVAILABLE;
1793   for (i = 0; i < MAX_PLAYERS; i++)
1794     joystick.nr[i] = -1;                /* no joystick configured */
1795
1796   SDLInitJoysticks();
1797 }
1798
1799 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1800 {
1801   return SDLReadJoystick(nr, x, y, b1, b2);
1802 }
1803
1804 boolean CheckJoystickOpened(int nr)
1805 {
1806   return SDLCheckJoystickOpened(nr);
1807 }
1808
1809 void ClearJoystickState()
1810 {
1811   SDLClearJoystickState();
1812 }