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