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