changed request dialog to disable virtual buttons instead of hiding them
[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 GetOverlayEnabled(void)
421 {
422   return overlay.enabled;
423 }
424
425 boolean GetOverlayActive(void)
426 {
427   return overlay.active;
428 }
429
430 void SetDrawDeactivationMask(int draw_deactivation_mask)
431 {
432   gfx.draw_deactivation_mask = draw_deactivation_mask;
433 }
434
435 int GetDrawDeactivationMask(void)
436 {
437   return gfx.draw_deactivation_mask;
438 }
439
440 void SetDrawBackgroundMask(int draw_background_mask)
441 {
442   gfx.draw_background_mask = draw_background_mask;
443 }
444
445 static void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask)
446 {
447   if (background_bitmap_tile != NULL)
448     gfx.background_bitmap_mask |= mask;
449   else
450     gfx.background_bitmap_mask &= ~mask;
451
452   if (background_bitmap_tile == NULL)   // empty background requested
453     return;
454
455   if (mask == REDRAW_ALL)
456     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
457                     0, 0, video.width, video.height);
458   else if (mask == REDRAW_FIELD)
459     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
460                     gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize);
461   else if (mask == REDRAW_DOOR_1)
462     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
463                     gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize);
464 }
465
466 void SetWindowBackgroundBitmap(Bitmap *background_bitmap_tile)
467 {
468   // remove every mask before setting mask for window
469   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
470   SetBackgroundBitmap(NULL, 0xffff);            // !!! FIX THIS !!!
471   SetBackgroundBitmap(background_bitmap_tile, REDRAW_ALL);
472 }
473
474 void SetMainBackgroundBitmap(Bitmap *background_bitmap_tile)
475 {
476   // remove window area mask before setting mask for main area
477   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
478   SetBackgroundBitmap(NULL, REDRAW_ALL);        // !!! FIX THIS !!!
479   SetBackgroundBitmap(background_bitmap_tile, REDRAW_FIELD);
480 }
481
482 void SetDoorBackgroundBitmap(Bitmap *background_bitmap_tile)
483 {
484   // remove window area mask before setting mask for door area
485   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
486   SetBackgroundBitmap(NULL, REDRAW_ALL);        // !!! FIX THIS !!!
487   SetBackgroundBitmap(background_bitmap_tile, REDRAW_DOOR_1);
488 }
489
490
491 // ============================================================================
492 // video functions
493 // ============================================================================
494
495 static int GetRealDepth(int depth)
496 {
497   return (depth == DEFAULT_DEPTH ? video.default_depth : depth);
498 }
499
500 static void sysFillRectangle(Bitmap *bitmap, int x, int y,
501                              int width, int height, Pixel color)
502 {
503   SDLFillRectangle(bitmap, x, y, width, height, color);
504
505   if (bitmap == backbuffer)
506     SetRedrawMaskFromArea(x, y, width, height);
507 }
508
509 static void sysCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
510                         int src_x, int src_y, int width, int height,
511                         int dst_x, int dst_y, int mask_mode)
512 {
513   SDLCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
514               dst_x, dst_y, mask_mode);
515
516   if (dst_bitmap == backbuffer)
517     SetRedrawMaskFromArea(dst_x, dst_y, width, height);
518 }
519
520 void LimitScreenUpdates(boolean enable)
521 {
522   SDLLimitScreenUpdates(enable);
523 }
524
525 void InitVideoDefaults(void)
526 {
527   video.default_depth = 32;
528 }
529
530 void InitVideoDisplay(void)
531 {
532   if (program.headless)
533     return;
534
535   SDLInitVideoDisplay();
536   SDLSetDisplaySize();
537 }
538
539 void CloseVideoDisplay(void)
540 {
541   KeyboardAutoRepeatOn();
542
543   SDL_QuitSubSystem(SDL_INIT_VIDEO);
544 }
545
546 void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
547 {
548   video.width = width;
549   video.height = height;
550   video.depth = GetRealDepth(depth);
551
552   video.screen_width = width;
553   video.screen_height = height;
554   video.screen_xoffset = 0;
555   video.screen_yoffset = 0;
556
557   video.fullscreen_available = FULLSCREEN_STATUS;
558   video.fullscreen_enabled = FALSE;
559
560   video.window_scaling_available = WINDOW_SCALING_STATUS;
561
562   video.frame_delay = 0;
563   video.frame_delay_value = GAME_FRAME_DELAY;
564
565   video.shifted_up = FALSE;
566   video.shifted_up_pos = 0;
567   video.shifted_up_pos_last = 0;
568   video.shifted_up_delay = 0;
569   video.shifted_up_delay_value = ONE_SECOND_DELAY / 4;
570
571   SDLInitVideoBuffer(fullscreen);
572
573   video.initialized = !program.headless;
574
575   drawto = backbuffer;
576 }
577
578 static void FreeBitmapPointers(Bitmap *bitmap)
579 {
580   if (bitmap == NULL)
581     return;
582
583   SDLFreeBitmapPointers(bitmap);
584
585   checked_free(bitmap->source_filename);
586   bitmap->source_filename = NULL;
587 }
588
589 static void TransferBitmapPointers(Bitmap *src_bitmap,
590                                    Bitmap *dst_bitmap)
591 {
592   if (src_bitmap == NULL || dst_bitmap == NULL)
593     return;
594
595   FreeBitmapPointers(dst_bitmap);
596
597   *dst_bitmap = *src_bitmap;
598 }
599
600 void FreeBitmap(Bitmap *bitmap)
601 {
602   if (bitmap == NULL)
603     return;
604
605   FreeBitmapPointers(bitmap);
606
607   free(bitmap);
608 }
609
610 Bitmap *CreateBitmapStruct(void)
611 {
612   return checked_calloc(sizeof(Bitmap));
613 }
614
615 Bitmap *CreateBitmap(int width, int height, int depth)
616 {
617   Bitmap *new_bitmap = CreateBitmapStruct();
618   int real_width  = MAX(1, width);      // prevent zero bitmap width
619   int real_height = MAX(1, height);     // prevent zero bitmap height
620   int real_depth  = GetRealDepth(depth);
621
622   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
623
624   new_bitmap->width  = real_width;
625   new_bitmap->height = real_height;
626
627   return new_bitmap;
628 }
629
630 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
631 {
632   if (*bitmap != NULL)
633   {
634     // if new bitmap size fits into old one, no need to re-create it
635     if (width  <= (*bitmap)->width &&
636         height <= (*bitmap)->height)
637       return;
638
639     // else adjust size so that old and new bitmap size fit into it
640     width  = MAX(width,  (*bitmap)->width);
641     height = MAX(height, (*bitmap)->height);
642   }
643
644   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
645
646   if (*bitmap == NULL)
647   {
648     *bitmap = new_bitmap;
649   }
650   else
651   {
652     TransferBitmapPointers(new_bitmap, *bitmap);
653     free(new_bitmap);
654   }
655 }
656
657 #if 0
658 static void CloseWindow(DrawWindow *window)
659 {
660 }
661 #endif
662
663 void SetRedrawMaskFromArea(int x, int y, int width, int height)
664 {
665   int x1 = x;
666   int y1 = y;
667   int x2 = x + width - 1;
668   int y2 = y + height - 1;
669
670   if (width == 0 || height == 0)
671     return;
672
673   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
674     redraw_mask |= REDRAW_FIELD;
675   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
676     redraw_mask |= REDRAW_DOOR_1;
677   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
678     redraw_mask |= REDRAW_DOOR_2;
679   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
680     redraw_mask |= REDRAW_DOOR_3;
681   else
682     redraw_mask = REDRAW_ALL;
683 }
684
685 static boolean CheckDrawingArea(int x, int y, int width, int height,
686                                 int draw_mask)
687 {
688   if (draw_mask == REDRAW_NONE)
689     return FALSE;
690
691   if (draw_mask & REDRAW_ALL)
692     return TRUE;
693
694   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
695     return TRUE;
696
697   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
698     return TRUE;
699
700   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
701     return TRUE;
702
703   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
704     return TRUE;
705
706   return FALSE;
707 }
708
709 boolean DrawingDeactivatedField(void)
710 {
711   if (program.headless)
712     return TRUE;
713
714   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
715     return TRUE;
716
717   return FALSE;
718 }
719
720 boolean DrawingDeactivated(int x, int y, int width, int height)
721 {
722   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
723 }
724
725 boolean DrawingOnBackground(int x, int y)
726 {
727   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
728           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
729 }
730
731 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
732                                   int *width, int *height, boolean is_dest)
733 {
734   int clip_x, clip_y, clip_width, clip_height;
735
736   if (gfx.clipping_enabled && is_dest)  // only clip destination bitmap
737   {
738     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
739     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
740     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
741     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
742   }
743   else
744   {
745     clip_x = 0;
746     clip_y = 0;
747     clip_width = bitmap->width;
748     clip_height = bitmap->height;
749   }
750
751   // skip if rectangle completely outside bitmap
752
753   if (*x + *width  <= clip_x ||
754       *y + *height <= clip_y ||
755       *x >= clip_x + clip_width ||
756       *y >= clip_y + clip_height)
757     return FALSE;
758
759   // clip if rectangle overlaps bitmap
760
761   if (*x < clip_x)
762   {
763     *width -= clip_x - *x;
764     *x = clip_x;
765   }
766   else if (*x + *width > clip_x + clip_width)
767   {
768     *width = clip_x + clip_width - *x;
769   }
770
771   if (*y < clip_y)
772   {
773     *height -= clip_y - *y;
774     *y = clip_y;
775   }
776   else if (*y + *height > clip_y + clip_height)
777   {
778     *height = clip_y + clip_height - *y;
779   }
780
781   return TRUE;
782 }
783
784 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
785                 int src_x, int src_y, int width, int height,
786                 int dst_x, int dst_y)
787 {
788   int dst_x_unclipped = dst_x;
789   int dst_y_unclipped = dst_y;
790
791   if (program.headless)
792     return;
793
794   if (src_bitmap == NULL || dst_bitmap == NULL)
795     return;
796
797   if (DrawingDeactivated(dst_x, dst_y, width, height))
798     return;
799
800   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
801       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
802     return;
803
804   // source x/y might need adjustment if destination x/y was clipped top/left
805   src_x += dst_x - dst_x_unclipped;
806   src_y += dst_y - dst_y_unclipped;
807
808   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
809   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
810   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
811      but is already fixed in SVN and should therefore finally be fixed with
812      the next official SDL release, which is probably version 1.2.14.) */
813   // !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!!
814
815   if (src_bitmap == dst_bitmap)
816   {
817     // needed when blitting directly to same bitmap -- should not be needed with
818     // recent SDL libraries, but apparently does not work in 1.2.11 directly
819
820     static Bitmap *tmp_bitmap = NULL;
821     static int tmp_bitmap_xsize = 0;
822     static int tmp_bitmap_ysize = 0;
823
824     // start with largest static bitmaps for initial bitmap size ...
825     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
826     {
827       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
828       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
829     }
830
831     // ... and allow for later re-adjustments due to custom artwork bitmaps
832     if (src_bitmap->width > tmp_bitmap_xsize ||
833         src_bitmap->height > tmp_bitmap_ysize)
834     {
835       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
836       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
837
838       FreeBitmap(tmp_bitmap);
839
840       tmp_bitmap = NULL;
841     }
842
843     if (tmp_bitmap == NULL)
844       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
845                                 DEFAULT_DEPTH);
846
847     sysCopyArea(src_bitmap, tmp_bitmap,
848                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
849     sysCopyArea(tmp_bitmap, dst_bitmap,
850                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
851
852     return;
853   }
854
855   sysCopyArea(src_bitmap, dst_bitmap,
856               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
857 }
858
859 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
860                      int src_x, int src_y, int src_width, int src_height,
861                      int dst_x, int dst_y, int dst_width, int dst_height)
862 {
863   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
864   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
865   int dst_xsize = dst_width;
866   int dst_ysize = dst_height;
867   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
868   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
869   int x, y;
870
871   for (y = 0; y < src_ysteps; y++)
872   {
873     for (x = 0; x < src_xsteps; x++)
874     {
875       int draw_x = dst_x + x * src_xsize;
876       int draw_y = dst_y + y * src_ysize;
877       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
878       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
879
880       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
881                  draw_x, draw_y);
882     }
883   }
884 }
885
886 void FadeRectangle(int x, int y, int width, int height,
887                    int fade_mode, int fade_delay, int post_delay,
888                    void (*draw_border_function)(void))
889 {
890   // (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined)
891   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
892     return;
893
894   SDLFadeRectangle(x, y, width, height,
895                    fade_mode, fade_delay, post_delay, draw_border_function);
896 }
897
898 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
899                    Pixel color)
900 {
901   if (DrawingDeactivated(x, y, width, height))
902     return;
903
904   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
905     return;
906
907   sysFillRectangle(bitmap, x, y, width, height, color);
908 }
909
910 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
911 {
912   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
913 }
914
915 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
916                                 int width, int height)
917 {
918   if (DrawingOnBackground(x, y))
919     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
920   else
921     ClearRectangle(bitmap, x, y, width, height);
922 }
923
924 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
925                       int src_x, int src_y, int width, int height,
926                       int dst_x, int dst_y)
927 {
928   if (DrawingDeactivated(dst_x, dst_y, width, height))
929     return;
930
931   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
932               dst_x, dst_y, BLIT_MASKED);
933 }
934
935 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
936                             int src_x, int src_y, int width, int height,
937                             int dst_x, int dst_y)
938 {
939   if (DrawingOnBackground(dst_x, dst_y))
940   {
941     // draw background
942     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
943                dst_x, dst_y);
944
945     // draw foreground
946     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
947                      dst_x, dst_y);
948   }
949   else
950     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
951                dst_x, dst_y);
952 }
953
954 void BlitTexture(Bitmap *bitmap,
955                 int src_x, int src_y, int width, int height,
956                 int dst_x, int dst_y)
957 {
958   if (bitmap == NULL)
959     return;
960
961   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
962                  BLIT_OPAQUE);
963 }
964
965 void BlitTextureMasked(Bitmap *bitmap,
966                        int src_x, int src_y, int width, int height,
967                        int dst_x, int dst_y)
968 {
969   if (bitmap == NULL)
970     return;
971
972   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
973                  BLIT_MASKED);
974 }
975
976 void BlitToScreen(Bitmap *bitmap,
977                   int src_x, int src_y, int width, int height,
978                   int dst_x, int dst_y)
979 {
980   if (bitmap == NULL)
981     return;
982
983   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
984     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
985                width, height, dst_x, dst_y);
986   else
987     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
988 }
989
990 void BlitToScreenMasked(Bitmap *bitmap,
991                         int src_x, int src_y, int width, int height,
992                         int dst_x, int dst_y)
993 {
994   if (bitmap == NULL)
995     return;
996
997   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
998     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
999                      width, height, dst_x, dst_y);
1000   else
1001     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1002 }
1003
1004 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
1005                          int to_x, int to_y)
1006 {
1007   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
1008 }
1009
1010 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1011                          int to_x, int to_y)
1012 {
1013   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1014 }
1015
1016 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1017                      int to_x, int to_y, Pixel pixel, int line_width)
1018 {
1019   int x, y;
1020
1021   if (program.headless)
1022     return;
1023
1024   for (x = 0; x < line_width; x++)
1025   {
1026     for (y = 0; y < line_width; y++)
1027     {
1028       int dx = x - line_width / 2;
1029       int dy = y - line_width / 2;
1030
1031       if ((x == 0 && y == 0) ||
1032           (x == 0 && y == line_width - 1) ||
1033           (x == line_width - 1 && y == 0) ||
1034           (x == line_width - 1 && y == line_width - 1))
1035         continue;
1036
1037       SDLDrawLine(bitmap,
1038                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1039     }
1040   }
1041 }
1042
1043 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1044 {
1045   int line_width = 4;
1046   int i;
1047
1048   for (i = 0; i < num_points - 1; i++)
1049     DrawLine(bitmap, points[i].x, points[i].y,
1050              points[i + 1].x, points[i + 1].y, pixel, line_width);
1051
1052   /*
1053   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1054   */
1055 }
1056
1057 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1058 {
1059   if (program.headless)
1060     return BLACK_PIXEL;
1061
1062   if (x < 0 || x >= bitmap->width ||
1063       y < 0 || y >= bitmap->height)
1064     return BLACK_PIXEL;
1065
1066   return SDLGetPixel(bitmap, x, y);
1067 }
1068
1069 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1070                       unsigned int color_g, unsigned int color_b)
1071 {
1072   if (program.headless)
1073     return BLACK_PIXEL;
1074
1075   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1076 }
1077
1078 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
1079 {
1080   unsigned int color_r = (color >> 16) & 0xff;
1081   unsigned int color_g = (color >>  8) & 0xff;
1082   unsigned int color_b = (color >>  0) & 0xff;
1083
1084   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
1085 }
1086
1087 void KeyboardAutoRepeatOn(void)
1088 {
1089   keyrepeat_status = TRUE;
1090 }
1091
1092 void KeyboardAutoRepeatOff(void)
1093 {
1094   keyrepeat_status = FALSE;
1095 }
1096
1097 boolean SetVideoMode(boolean fullscreen)
1098 {
1099   return SDLSetVideoMode(fullscreen);
1100 }
1101
1102 void SetVideoFrameDelay(unsigned int frame_delay_value)
1103 {
1104   video.frame_delay_value = frame_delay_value;
1105 }
1106
1107 unsigned int GetVideoFrameDelay(void)
1108 {
1109   return video.frame_delay_value;
1110 }
1111
1112 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1113 {
1114   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1115       (!fullscreen && video.fullscreen_enabled))
1116     fullscreen = SetVideoMode(fullscreen);
1117
1118   return fullscreen;
1119 }
1120
1121 Bitmap *LoadImage(char *filename)
1122 {
1123   Bitmap *new_bitmap;
1124
1125   new_bitmap = SDLLoadImage(filename);
1126
1127   if (new_bitmap)
1128     new_bitmap->source_filename = getStringCopy(filename);
1129
1130   return new_bitmap;
1131 }
1132
1133 Bitmap *LoadCustomImage(char *basename)
1134 {
1135   char *filename = getCustomImageFilename(basename);
1136   Bitmap *new_bitmap;
1137
1138   if (filename == NULL)
1139     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1140
1141   if ((new_bitmap = LoadImage(filename)) == NULL)
1142     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1143
1144   return new_bitmap;
1145 }
1146
1147 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1148 {
1149   char *filename = getCustomImageFilename(basename);
1150   Bitmap *new_bitmap;
1151
1152   if (filename == NULL)         // (should never happen)
1153   {
1154     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1155     return;
1156   }
1157
1158   if (strEqual(filename, bitmap->source_filename))
1159   {
1160     // The old and new image are the same (have the same filename and path).
1161     // This usually means that this image does not exist in this graphic set
1162     // and a fallback to the existing image is done.
1163
1164     return;
1165   }
1166
1167   if ((new_bitmap = LoadImage(filename)) == NULL)
1168   {
1169     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1170     return;
1171   }
1172
1173   if (bitmap->width != new_bitmap->width ||
1174       bitmap->height != new_bitmap->height)
1175   {
1176     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1177           filename);
1178     FreeBitmap(new_bitmap);
1179     return;
1180   }
1181
1182   TransferBitmapPointers(new_bitmap, bitmap);
1183   free(new_bitmap);
1184 }
1185
1186 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1187 {
1188   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1189 }
1190
1191 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1192 {
1193   if (bitmaps[IMG_BITMAP_CUSTOM])
1194   {
1195     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1196
1197     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1198   }
1199
1200   if (gfx.game_tile_size == gfx.standard_tile_size)
1201   {
1202     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1203
1204     return;
1205   }
1206
1207   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1208   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1209   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1210
1211   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1212
1213   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1214   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1215 }
1216
1217 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1218                                 int tile_size, boolean create_small_bitmaps)
1219 {
1220   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1221   Bitmap *tmp_bitmap_final = NULL;
1222   Bitmap *tmp_bitmap_0 = NULL;
1223   Bitmap *tmp_bitmap_1 = NULL;
1224   Bitmap *tmp_bitmap_2 = NULL;
1225   Bitmap *tmp_bitmap_4 = NULL;
1226   Bitmap *tmp_bitmap_8 = NULL;
1227   Bitmap *tmp_bitmap_16 = NULL;
1228   Bitmap *tmp_bitmap_32 = NULL;
1229   int width_final, height_final;
1230   int width_0, height_0;
1231   int width_1, height_1;
1232   int width_2, height_2;
1233   int width_4, height_4;
1234   int width_8, height_8;
1235   int width_16, height_16;
1236   int width_32, height_32;
1237   int old_width, old_height;
1238   int i;
1239
1240   print_timestamp_init("CreateScaledBitmaps");
1241
1242   old_width  = old_bitmap->width;
1243   old_height = old_bitmap->height;
1244
1245   // calculate new image dimensions for final image size
1246   width_final  = old_width  * zoom_factor;
1247   height_final = old_height * zoom_factor;
1248
1249   // get image with final size (this might require scaling up)
1250   // ("final" size may result in non-standard tile size image)
1251   if (zoom_factor != 1)
1252     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1253   else
1254     tmp_bitmap_final = old_bitmap;
1255
1256   UPDATE_BUSY_STATE();
1257
1258   width_0  = width_1  = width_final;
1259   height_0 = height_1 = height_final;
1260
1261   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1262
1263   if (create_small_bitmaps)
1264   {
1265     // check if we have a non-gameplay tile size image
1266     if (tile_size != gfx.game_tile_size)
1267     {
1268       // get image with gameplay tile size
1269       width_0  = width_final  * gfx.game_tile_size / tile_size;
1270       height_0 = height_final * gfx.game_tile_size / tile_size;
1271
1272       if (width_0 == old_width)
1273         tmp_bitmap_0 = old_bitmap;
1274       else if (width_0 == width_final)
1275         tmp_bitmap_0 = tmp_bitmap_final;
1276       else
1277         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1278
1279       UPDATE_BUSY_STATE();
1280     }
1281
1282     // check if we have a non-standard tile size image
1283     if (tile_size != gfx.standard_tile_size)
1284     {
1285       // get image with standard tile size
1286       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1287       height_1 = height_final * gfx.standard_tile_size / tile_size;
1288
1289       if (width_1 == old_width)
1290         tmp_bitmap_1 = old_bitmap;
1291       else if (width_1 == width_final)
1292         tmp_bitmap_1 = tmp_bitmap_final;
1293       else if (width_1 == width_0)
1294         tmp_bitmap_1 = tmp_bitmap_0;
1295       else
1296         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1297
1298       UPDATE_BUSY_STATE();
1299     }
1300
1301     // calculate new image dimensions for small images
1302     width_2  = width_1  / 2;
1303     height_2 = height_1 / 2;
1304     width_4  = width_1  / 4;
1305     height_4 = height_1 / 4;
1306     width_8  = width_1  / 8;
1307     height_8 = height_1 / 8;
1308     width_16  = width_1  / 16;
1309     height_16 = height_1 / 16;
1310     width_32  = width_1  / 32;
1311     height_32 = height_1 / 32;
1312
1313     // get image with 1/2 of normal size (for use in the level editor)
1314     if (width_2 == old_width)
1315       tmp_bitmap_2 = old_bitmap;
1316     else
1317       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1318
1319     UPDATE_BUSY_STATE();
1320
1321     // get image with 1/4 of normal size (for use in the level editor)
1322     if (width_4 == old_width)
1323       tmp_bitmap_4 = old_bitmap;
1324     else
1325       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1326
1327     UPDATE_BUSY_STATE();
1328
1329     // get image with 1/8 of normal size (for use on the preview screen)
1330     if (width_8 == old_width)
1331       tmp_bitmap_8 = old_bitmap;
1332     else
1333       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1334
1335     UPDATE_BUSY_STATE();
1336
1337     // get image with 1/16 of normal size (for use on the preview screen)
1338     if (width_16 == old_width)
1339       tmp_bitmap_16 = old_bitmap;
1340     else
1341       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1342
1343     UPDATE_BUSY_STATE();
1344
1345     // get image with 1/32 of normal size (for use on the preview screen)
1346     if (width_32 == old_width)
1347       tmp_bitmap_32 = old_bitmap;
1348     else
1349       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1350
1351     UPDATE_BUSY_STATE();
1352
1353     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1354     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1355     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1356     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1357     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1358     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1359
1360     if (width_0 != width_1)
1361       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1362
1363     if (bitmaps[IMG_BITMAP_CUSTOM])
1364       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1365     else
1366       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1367
1368     boolean free_old_bitmap = TRUE;
1369
1370     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1371       if (bitmaps[i] == old_bitmap)
1372         free_old_bitmap = FALSE;
1373
1374     if (free_old_bitmap)
1375     {
1376       // copy image filename from old to new standard sized bitmap
1377       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1378         getStringCopy(old_bitmap->source_filename);
1379
1380       FreeBitmap(old_bitmap);
1381     }
1382   }
1383   else
1384   {
1385     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1386   }
1387
1388   UPDATE_BUSY_STATE();
1389
1390   print_timestamp_done("CreateScaledBitmaps");
1391 }
1392
1393 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1394                                   int tile_size)
1395 {
1396   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1397 }
1398
1399 void CreateBitmapTextures(Bitmap **bitmaps)
1400 {
1401   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1402 }
1403
1404 void FreeBitmapTextures(Bitmap **bitmaps)
1405 {
1406   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1407 }
1408
1409 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1410 {
1411   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1412 }
1413
1414
1415 // ----------------------------------------------------------------------------
1416 // mouse pointer functions
1417 // ----------------------------------------------------------------------------
1418
1419 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1420
1421 // XPM image definitions
1422 static const char *cursor_image_none[] =
1423 {
1424   // width height num_colors chars_per_pixel
1425   "    16    16        3            1",
1426
1427   // colors
1428   "X c #000000",
1429   ". c #ffffff",
1430   "  c None",
1431
1432   // pixels
1433   "                ",
1434   "                ",
1435   "                ",
1436   "                ",
1437   "                ",
1438   "                ",
1439   "                ",
1440   "                ",
1441   "                ",
1442   "                ",
1443   "                ",
1444   "                ",
1445   "                ",
1446   "                ",
1447   "                ",
1448   "                ",
1449
1450   // hot spot
1451   "0,0"
1452 };
1453
1454 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1455 static const char *cursor_image_dot[] =
1456 {
1457   // width height num_colors chars_per_pixel
1458   "    16    16        3            1",
1459
1460   // colors
1461   "X c #000000",
1462   ". c #ffffff",
1463   "  c None",
1464
1465   // pixels
1466   " X              ",
1467   "X.X             ",
1468   " X              ",
1469   "                ",
1470   "                ",
1471   "                ",
1472   "                ",
1473   "                ",
1474   "                ",
1475   "                ",
1476   "                ",
1477   "                ",
1478   "                ",
1479   "                ",
1480   "                ",
1481   "                ",
1482
1483   // hot spot
1484   "1,1"
1485 };
1486 static const char **cursor_image_playfield = cursor_image_dot;
1487 #else
1488 // some people complained about a "white dot" on the screen and thought it
1489 // was a graphical error... OK, let's just remove the whole pointer :-)
1490 static const char **cursor_image_playfield = cursor_image_none;
1491 #endif
1492
1493 static const int cursor_bit_order = BIT_ORDER_MSB;
1494
1495 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1496 {
1497   struct MouseCursorInfo *cursor;
1498   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1499   int header_lines = 4;
1500   int x, y, i;
1501
1502   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1503
1504   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1505
1506   i = -1;
1507   for (y = 0; y < cursor->width; y++)
1508   {
1509     for (x = 0; x < cursor->height; x++)
1510     {
1511       int bit_nr = x % 8;
1512       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1513
1514       if (bit_nr == 0)
1515       {
1516         i++;
1517         cursor->data[i] = cursor->mask[i] = 0;
1518       }
1519
1520       switch (image[header_lines + y][x])
1521       {
1522         case 'X':
1523           cursor->data[i] |= bit_mask;
1524           cursor->mask[i] |= bit_mask;
1525           break;
1526
1527         case '.':
1528           cursor->mask[i] |= bit_mask;
1529           break;
1530
1531         case ' ':
1532           break;
1533       }
1534     }
1535   }
1536
1537   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1538
1539   return cursor;
1540 }
1541
1542 void SetMouseCursor(int mode)
1543 {
1544   static struct MouseCursorInfo *cursor_none = NULL;
1545   static struct MouseCursorInfo *cursor_playfield = NULL;
1546   struct MouseCursorInfo *cursor_new;
1547
1548   if (cursor_none == NULL)
1549     cursor_none = get_cursor_from_image(cursor_image_none);
1550
1551   if (cursor_playfield == NULL)
1552     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1553
1554   cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
1555                 mode == CURSOR_NONE      ? cursor_none :
1556                 mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1557
1558   SDLSetMouseCursor(cursor_new);
1559
1560   gfx.cursor_mode = mode;
1561 }
1562
1563
1564 // ============================================================================
1565 // audio functions
1566 // ============================================================================
1567
1568 void OpenAudio(void)
1569 {
1570   // always start with reliable default values
1571   audio.sound_available = FALSE;
1572   audio.music_available = FALSE;
1573   audio.loops_available = FALSE;
1574
1575   audio.sound_enabled = FALSE;
1576   audio.sound_deactivated = FALSE;
1577
1578   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1579   audio.mixer_pid = 0;
1580   audio.device_name = NULL;
1581   audio.device_fd = -1;
1582
1583   audio.num_channels = 0;
1584   audio.music_channel = 0;
1585   audio.first_sound_channel = 0;
1586
1587   SDLOpenAudio();
1588 }
1589
1590 void CloseAudio(void)
1591 {
1592   SDLCloseAudio();
1593
1594   audio.sound_enabled = FALSE;
1595 }
1596
1597 void SetAudioMode(boolean enabled)
1598 {
1599   if (!audio.sound_available)
1600     return;
1601
1602   audio.sound_enabled = enabled;
1603 }
1604
1605
1606 // ============================================================================
1607 // event functions
1608 // ============================================================================
1609
1610 boolean PendingEvent(void)
1611 {
1612   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1613 }
1614
1615 void WaitEvent(Event *event)
1616 {
1617   SDLWaitEvent(event);
1618 }
1619
1620 void PeekEvent(Event *event)
1621 {
1622   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1623 }
1624
1625 void CheckQuitEvent(void)
1626 {
1627   if (SDL_QuitRequested())
1628     program.exit_function(0);
1629 }
1630
1631 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1632 {
1633   // key up/down events in SDL2 do not return text characters anymore
1634   return event->keysym.sym;
1635 }
1636
1637 KeyMod HandleKeyModState(Key key, int key_status)
1638 {
1639   static KeyMod current_modifiers = KMOD_None;
1640
1641   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1642   {
1643     KeyMod new_modifier = KMOD_None;
1644
1645     switch(key)
1646     {
1647       case KSYM_Shift_L:
1648         new_modifier = KMOD_Shift_L;
1649         break;
1650       case KSYM_Shift_R:
1651         new_modifier = KMOD_Shift_R;
1652         break;
1653       case KSYM_Control_L:
1654         new_modifier = KMOD_Control_L;
1655         break;
1656       case KSYM_Control_R:
1657         new_modifier = KMOD_Control_R;
1658         break;
1659       case KSYM_Meta_L:
1660         new_modifier = KMOD_Meta_L;
1661         break;
1662       case KSYM_Meta_R:
1663         new_modifier = KMOD_Meta_R;
1664         break;
1665       case KSYM_Alt_L:
1666         new_modifier = KMOD_Alt_L;
1667         break;
1668       case KSYM_Alt_R:
1669         new_modifier = KMOD_Alt_R;
1670         break;
1671       default:
1672         break;
1673     }
1674
1675     if (key_status == KEY_PRESSED)
1676       current_modifiers |= new_modifier;
1677     else
1678       current_modifiers &= ~new_modifier;
1679   }
1680
1681   return current_modifiers;
1682 }
1683
1684 KeyMod GetKeyModState(void)
1685 {
1686   return (KeyMod)SDL_GetModState();
1687 }
1688
1689 KeyMod GetKeyModStateFromEvents(void)
1690 {
1691   /* always use key modifier state as tracked from key events (this is needed
1692      if the modifier key event was injected into the event queue, but the key
1693      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1694      query the keys as held pressed on the keyboard) -- this case is currently
1695      only used to filter out clipboard insert events from "True X-Mouse" tool */
1696
1697   return HandleKeyModState(KSYM_UNDEFINED, 0);
1698 }
1699
1700 void StartTextInput(int x, int y, int width, int height)
1701 {
1702 #if defined(HAS_SCREEN_KEYBOARD)
1703   SDL_StartTextInput();
1704
1705   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1706   {
1707     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1708     video.shifted_up_delay = SDL_GetTicks();
1709     video.shifted_up = TRUE;
1710   }
1711 #endif
1712 }
1713
1714 void StopTextInput(void)
1715 {
1716 #if defined(HAS_SCREEN_KEYBOARD)
1717   SDL_StopTextInput();
1718
1719   if (video.shifted_up)
1720   {
1721     video.shifted_up_pos = 0;
1722     video.shifted_up_delay = SDL_GetTicks();
1723     video.shifted_up = FALSE;
1724   }
1725 #endif
1726 }
1727
1728 boolean CheckCloseWindowEvent(ClientMessageEvent *event)
1729 {
1730   if (event->type != EVENT_CLIENTMESSAGE)
1731     return FALSE;
1732
1733   return TRUE;          // the only possible message here is SDL_QUIT
1734 }
1735
1736
1737 // ============================================================================
1738 // joystick functions
1739 // ============================================================================
1740
1741 void InitJoysticks(void)
1742 {
1743   int i;
1744
1745 #if defined(NO_JOYSTICK)
1746   return;       // joysticks generally deactivated by compile-time directive
1747 #endif
1748
1749   // always start with reliable default values
1750   joystick.status = JOYSTICK_NOT_AVAILABLE;
1751   for (i = 0; i < MAX_PLAYERS; i++)
1752     joystick.nr[i] = -1;                // no joystick configured
1753
1754   SDLInitJoysticks();
1755 }
1756
1757 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1758 {
1759   return SDLReadJoystick(nr, x, y, b1, b2);
1760 }
1761
1762 boolean CheckJoystickOpened(int nr)
1763 {
1764   return SDLCheckJoystickOpened(nr);
1765 }
1766
1767 void ClearJoystickState(void)
1768 {
1769   SDLClearJoystickState();
1770 }