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