20100212
[gdash.git] / src / sdlui.c
blob6402ddb4ff52c73d816ffcc4ce873bedd1cccb53
1 /*
2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <SDL.h>
17 #include <SDL_image.h>
18 #include <glib.h>
19 #include <glib/gstdio.h>
20 #include "cavedb.h"
21 #include "config.h"
22 #include "cave.h"
23 #include "caveset.h"
24 #include "sdlgfx.h"
25 #include "settings.h"
26 #include "util.h"
27 #include "about.h"
28 #include "sound.h"
30 #include "sdlui.h"
33 char *gd_last_folder=NULL;
34 char *gd_caveset_filename=NULL;
37 /* operation successful, so we should remember file name. */
38 void
39 gd_caveset_file_operation_successful(const char *filename)
41 /* if it is a bd file, remember new filename */
42 if (g_str_has_suffix(filename, ".bd")) {
43 char *stored;
45 /* first make copy, then free and set pointer. we might be called with filename=caveset_filename */
46 stored=g_strdup(filename);
47 g_free(gd_caveset_filename);
48 gd_caveset_filename=stored;
49 } else {
50 g_free(gd_caveset_filename);
51 gd_caveset_filename=NULL;
55 void
56 gd_open_caveset(const char *directory)
58 char *filename;
59 char *filter;
61 /* if the caveset is edited, ask the user if to save. */
62 /* if does not want to discard, get out here */
63 if (!gd_discard_changes())
64 return;
66 if (!gd_last_folder)
67 gd_last_folder=g_strdup(g_get_home_dir());
69 filter=g_strjoinv(";", gd_caveset_extensions);
70 filename=gd_select_file("SELECT CAVESET TO LOAD", directory?directory:gd_last_folder, filter, FALSE);
71 g_free(filter);
73 /* if file selected */
74 if (filename) {
75 /* remember last directory */
76 g_free(gd_last_folder);
77 gd_last_folder=g_path_get_dirname(filename);
79 gd_save_highscore(gd_user_config_dir);
81 gd_caveset_load_from_file(filename, gd_user_config_dir);
83 /* if successful loading and this is a bd file, and we load highscores from our own config dir */
84 if (!gd_has_new_error() && g_str_has_suffix(filename, ".bd") && !gd_use_bdcff_highscore)
85 gd_load_highscore(gd_user_config_dir);
87 g_free(filename);
95 static int
96 filenamesort(gconstpointer a, gconstpointer b)
98 gchar **a_=(gpointer) a, **b_=(gpointer) b;
99 return g_ascii_strcasecmp(*a_, *b_);
104 /* set title line (the first line in the screen) to text */
105 void
106 gd_title_line(const char *format, ...)
108 va_list args;
109 char *text;
111 va_start(args, format);
112 text=g_strdup_vprintf(format, args);
113 gd_blittext_n(gd_screen, -1, 0, GD_GDASH_WHITE, text);
114 g_free(text);
115 va_end(args);
118 /* set status line (the last line in the screen) to text */
119 void
120 gd_status_line(const char *text)
122 gd_blittext_n(gd_screen, -1, gd_screen->h-gd_font_height(), GD_GDASH_GRAY2, text);
125 void
126 gd_status_line_red(const char *text)
128 gd_blittext_n(gd_screen, -1, gd_screen->h-gd_font_height(), GD_GDASH_RED, text);
134 /* runs a file selection dialog */
135 /* returns new, full-path filename (to be freed later by the caller) */
136 /* glob: semicolon separated list of globs */
137 char *
138 gd_select_file(const char *title, const char *start_dir, const char *glob, gboolean for_save)
140 enum {
141 GD_NOT_YET,
142 GD_YES,
143 GD_JUMP,
144 GD_ESCAPE,
145 GD_QUIT,
146 GD_NEW,
149 static char *current_dir=NULL; /* static to not worry about freeing */
150 const int yd=gd_line_height();
151 const int names_per_page=gd_screen->h/yd-3;
152 char *result;
153 char *directory;
154 int filestate;
155 char **globs;
156 gboolean redraw;
158 if (glob==NULL || g_str_equal(glob, ""))
159 glob="*";
160 globs=g_strsplit_set(glob, ";", -1);
162 /* remember current directory, as we step into others */
163 if (current_dir)
164 g_free(current_dir);
165 current_dir=g_get_current_dir();
167 gd_backup_and_dark_screen();
168 gd_title_line(title);
169 if (for_save)
170 /* for saving, we allow the user to select a new filename. */
171 gd_status_line("CRSR:SELECT N:NEW J:JUMP ESC:CANCEL");
172 else
173 gd_status_line("MOVE: SELECT J: JUMP ESC: CANCEL");
175 /* this is somewhat hackish; finds out the absolute path of start_dir. also tests is we can enter that directory */
176 if (g_chdir(start_dir)==-1) {
177 g_warning("cannot change to directory: %s", start_dir);
178 /* stay in current_dir */
180 directory=g_get_current_dir();
181 g_chdir(current_dir);
183 result=NULL;
184 filestate=GD_NOT_YET;
185 while (!gd_quit && filestate==GD_NOT_YET) {
186 int sel, state;
187 GDir *dir;
188 GPtrArray *files;
189 const gchar *name;
191 /* read directory */
192 files=g_ptr_array_new();
193 if (g_chdir(directory)==-1) {
194 g_warning("cannot change to directory: %s", directory);
195 filestate=GD_ESCAPE;
196 break;
198 dir=g_dir_open(".", 0, NULL);
199 if (!dir) {
200 g_warning("cannot open directory: %s", directory);
201 filestate=GD_ESCAPE;
202 break;
204 while ((name=g_dir_read_name(dir))!=NULL) {
205 #ifdef G_OS_WIN32
206 /* on windows, skip hidden files? */
207 #else
208 /* on unix, skip file names starting with a '.' - those are hidden files */
209 if (name[0]=='.')
210 continue;
211 #endif
212 if (g_file_test(name, G_FILE_TEST_IS_DIR))
213 g_ptr_array_add(files, g_strdup_printf("%s%s", name, G_DIR_SEPARATOR_S));
214 else {
215 int i;
216 gboolean match=FALSE;
218 for (i=0; globs[i]!=NULL && match==FALSE; i++)
219 if (g_pattern_match_simple(globs[i], name))
220 match=TRUE;
222 if (match)
223 g_ptr_array_add(files, g_strdup(name));
226 g_dir_close(dir);
227 g_chdir(current_dir); /* step back to directory where we started */
229 /* add "directory up" and sort */
230 #ifdef G_OS_WIN32
231 /* if we are NOT in a root directory */
232 if (!g_str_has_suffix(directory, ":\\")) /* root directory is "X:\" */
233 g_ptr_array_add(files, g_strdup_printf("..%s", G_DIR_SEPARATOR_S));
234 #else
235 if (!g_str_equal(directory, "/"))
236 g_ptr_array_add(files, g_strdup_printf("..%s", G_DIR_SEPARATOR_S));
237 #endif
238 g_ptr_array_sort(files, filenamesort);
240 /* show current directory */
241 gd_clear_line(gd_screen, 1*yd);
242 gd_blittext_n(gd_screen, -1, 1*yd, GD_GDASH_YELLOW, gd_filename_to_utf8(directory));
244 /* do file selection menu */
245 sel=0;
246 state=GD_NOT_YET;
247 redraw=TRUE;
248 while (state==GD_NOT_YET) {
249 int page, i, cur;
250 SDL_Event event;
252 page=sel/names_per_page;
254 if (redraw) {
255 for (i=0, cur=page*names_per_page; i<names_per_page; i++, cur++) {
256 int col;
258 col=cur==sel?GD_GDASH_YELLOW:GD_GDASH_LIGHTBLUE;
260 gd_clear_line(gd_screen, (i+2)*yd);
261 if (cur<files->len) /* may not be as much filenames as it would fit on the screen */
262 gd_blittext_n(gd_screen, 0, (i+2)*yd, col, gd_filename_to_utf8(g_ptr_array_index(files, cur)));
264 SDL_Flip(gd_screen);
266 redraw=FALSE;
269 /* check for incoming events */
270 SDL_WaitEvent(NULL);
271 while (SDL_PollEvent(&event)) {
272 switch (event.type) {
273 case SDL_QUIT:
274 state=GD_QUIT;
275 gd_quit=TRUE;
276 break;
278 case SDL_KEYDOWN:
279 switch (event.key.keysym.sym) {
280 /* movements */
281 case SDLK_UP:
282 sel=gd_clamp(sel-1, 0, files->len-1);
283 redraw=TRUE;
284 break;
285 case SDLK_DOWN:
286 sel=gd_clamp(sel+1, 0, files->len-1);
287 redraw=TRUE;
288 break;
289 case SDLK_PAGEUP:
290 sel=gd_clamp(sel-names_per_page, 0, files->len-1);
291 redraw=TRUE;
292 break;
293 case SDLK_PAGEDOWN:
294 sel=gd_clamp(sel+names_per_page, 0, files->len-1);
295 redraw=TRUE;
296 break;
297 case SDLK_HOME:
298 sel=0;
299 redraw=TRUE;
300 break;
301 case SDLK_END:
302 sel=files->len-1;
303 redraw=TRUE;
304 break;
306 /* jump to directory (name will be typed) */
307 case SDLK_j:
308 state=GD_JUMP;
309 break;
310 /* enter new filename - only if saving allowed */
311 case SDLK_n:
312 if (for_save)
313 state=GD_NEW;
314 break;
316 /* select current file/directory */
317 case SDLK_SPACE:
318 case SDLK_RETURN:
319 state=GD_YES;
320 break;
322 case SDLK_ESCAPE:
323 state=GD_ESCAPE;
324 break;
326 default:
327 /* other keys do nothing */
328 break;
330 break;
332 default:
333 /* other events are not interesting now */
334 break;
335 } /* switch event.type */
336 } /* while pollevent */
337 } /* while state=nothing happened */
339 /* now check the state variable to see what happend. maybe act upon it. */
341 /* user requested to enter a new filename */
342 if (state==GD_NEW) {
343 char *name;
344 char *new_name;
345 char *extension_added;
347 /* make up a suggested filename */
348 if (!g_str_equal(gd_caveset_data->name, ""))
349 extension_added=g_strdup_printf("%s.bd", gd_caveset_data->name);
350 else
351 extension_added=NULL;
352 /* if extension added is null, g_build_path will sense that as the end of the list. */
353 name=g_build_path(G_DIR_SEPARATOR_S, directory, extension_added, NULL);
354 g_free(extension_added);
355 new_name=gd_input_string("ENTER NEW FILE NAME", name);
356 g_free(name);
357 /* if enters a file name, remember that, and exit the function via setting filestate variable */
358 if (new_name) {
359 result=new_name;
360 filestate=GD_YES;
361 } else
362 g_free(new_name);
365 /* user requested to ask for another directory name to jump to */
366 if (state==GD_JUMP) {
367 char *newdir;
369 newdir=gd_input_string("JUMP TO DIRECTORY", directory);
370 if (newdir) {
371 /* first change to dir, then to newdir: newdir entered by the user might not be absolute. */
372 if (g_chdir(directory)==-1 || g_chdir(newdir)==-1) {
373 g_warning("cannot change to directory: %s", newdir);
374 filestate=GD_ESCAPE;
375 g_free(newdir);
376 break;
379 g_free(directory);
380 directory=g_get_current_dir();
381 g_chdir(current_dir);
384 else
385 /* if selected any from the list, it can be a file or a directory. */
386 if (state==GD_YES) {
387 if (g_str_has_suffix(g_ptr_array_index(files, sel), G_DIR_SEPARATOR_S)) {
388 /* directory selected */
389 char *newdir;
391 if (g_chdir(directory)==-1) {
392 g_warning("cannot change to directory: %s", directory);
393 filestate=GD_ESCAPE;
394 break;
396 if (g_chdir(g_ptr_array_index(files, sel))==-1) {
397 g_warning("cannot change to directory: %s", (char *)g_ptr_array_index(files, sel));
398 filestate=GD_ESCAPE;
399 break;
401 newdir=g_get_current_dir();
402 g_chdir(current_dir); /* step back to directory where we started */
403 g_free(directory);
404 directory=newdir;
405 } else {
406 /* file selected */
407 result=g_build_path(G_DIR_SEPARATOR_S, directory, g_ptr_array_index(files, sel), NULL);
408 filestate=GD_YES;
410 } else
411 /* pass state to break loop */
412 filestate=state;
414 g_ptr_array_foreach(files, (GFunc) g_free, NULL);
415 g_ptr_array_free(files, TRUE);
418 /* if selecting a file to write to, check if overwrite */
419 if (filestate==GD_YES && result && for_save && g_file_test(result, G_FILE_TEST_EXISTS)) {
420 gboolean said_yes, answer;
422 answer=gd_ask_yes_no("File exists. Overwrite?", "No", "Yes", &said_yes);
423 if (!answer || !said_yes) { /* if did not answer or answered no, forget filename. we do not overwrite */
424 g_free(result);
425 result=NULL;
429 g_strfreev(globs);
430 gd_restore_screen();
432 return result;
441 * THEME HANDLING
444 static gboolean
445 is_image_ok_for_theme(const char *filename)
447 SDL_Surface *surface;
448 gboolean result=FALSE;
450 surface=IMG_Load(filename);
451 if (!surface)
452 return FALSE;
453 /* if the image is loaded */
454 if (surface) {
455 gd_error_set_context("%s", filename);
456 if (gd_is_surface_ok_for_theme(surface)) /* if image passes all checks, result is "OK" */
457 result=TRUE;
458 gd_error_set_context(NULL);
459 SDL_FreeSurface(surface);
462 return result;
466 static void
467 add_file_to_themes(GPtrArray *themes, const char *filename)
469 int i;
471 g_assert(filename!=NULL);
473 /* if file name already in themes list, remove. */
474 for (i=0; i<themes->len; i++)
475 if (g_ptr_array_index(themes, i)!=NULL && g_str_equal(g_ptr_array_index(themes, i), filename))
476 g_ptr_array_remove_index_fast(themes, i);
478 if (is_image_ok_for_theme(filename))
479 g_ptr_array_add(themes, g_strdup(filename));
483 static void
484 add_dir_to_themes(GPtrArray *themes, const char *directory_name)
486 GDir *dir;
487 const char *name;
489 dir=g_dir_open(directory_name, 0, NULL);
490 if (!dir)
491 /* silently ignore unable-to-open directories */
492 return;
493 while((name=g_dir_read_name(dir))) {
494 char *filename;
495 char *lower;
497 filename=g_build_filename(directory_name, name, NULL);
498 lower=g_ascii_strdown(filename, -1);
500 /* we only allow bmp and png files. converted to lowercase, to be able to check for .bmp */
501 if ((g_str_has_suffix(lower, ".bmp") || g_str_has_suffix(lower, ".png")) && g_file_test(filename, G_FILE_TEST_IS_REGULAR))
502 /* try to add the file. */
503 add_file_to_themes(themes, filename);
505 g_free(lower);
506 g_free(filename);
510 /* this is in glib 2.16, but that is too new for some users. */
511 /* also, this one treats NULL as the "lowest", so it will be at the start of the list */
512 static int
513 strcmp0(const char *str1, const char *str2)
515 if (str1==NULL && str2==NULL)
516 return 0;
517 if (str1==NULL)
518 return -1;
519 if (str2==NULL)
520 return 1;
521 return strcmp(str1, str2);
524 /* will create a list of file names which can be used as themes. */
525 /* the first item will be a NULL to represent the default, built-in theme. */
526 GPtrArray *
527 gd_create_themes_list()
529 GPtrArray *themes;
530 int i;
531 gboolean current_found;
533 themes=g_ptr_array_new();
534 g_ptr_array_add(themes, NULL); /* this symbolizes the default theme */
535 add_dir_to_themes(themes, gd_system_data_dir);
536 add_dir_to_themes(themes, gd_user_config_dir);
538 /* check if current theme is in the array */
539 current_found=FALSE;
540 for (i=0; i<themes->len; i++)
541 if (strcmp0(gd_sdl_theme, g_ptr_array_index(themes, i))==0)
542 current_found=TRUE;
543 if (!current_found)
544 add_file_to_themes(themes, gd_sdl_theme);
546 return themes;
551 * SDASH SETTINGS MENU
554 void
555 gd_settings_menu()
557 static GPtrArray *themes=NULL;
558 gboolean finished;
559 const char *yes="yes", *no="no";
560 int themenum;
561 typedef enum _settingtype {
562 TypeBoolean,
563 TypeScale,
564 TypeTheme,
565 TypePercent,
566 TypeStringv,
567 TypeKey,
568 } SettingType;
569 struct _setting {
570 int page;
571 SettingType type;
572 char *name;
573 void *var;
574 const char **stringv;
575 } settings[]= {
576 { 0, TypeBoolean, "Fullscreen", &gd_sdl_fullscreen },
577 { 0, TypeTheme, "Theme", NULL },
578 { 0, TypeScale, "Scale", &gd_sdl_scale },
579 { 0, TypeBoolean, "PAL emulation", &gd_sdl_pal_emulation },
580 { 0, TypePercent, "PAL scanline shade", &gd_pal_emu_scanline_shade },
581 { 0, TypeBoolean, "Even lines vertical scroll", &gd_even_line_pal_emu_vertical_scroll },
582 { 0, TypeBoolean, "Fine scrolling", &gd_fine_scroll },
583 { 0, TypeStringv, "C64 palette", &gd_c64_palette, gd_color_get_c64_palette_names() },
584 { 0, TypeStringv, "C64DTV palette", &gd_c64dtv_palette, gd_color_get_c64dtv_palette_names() },
585 { 0, TypeStringv, "Atari palette", &gd_atari_palette, gd_color_get_atari_palette_names() },
586 { 0, TypeStringv, "Preferred palette", &gd_preferred_palette, gd_color_get_palette_types_names() },
588 #ifdef GD_SOUND
589 { 1, TypeBoolean, "Sound", &gd_sdl_sound },
590 { 1, TypePercent, "Music volume", &gd_sound_music_volume_percent },
591 { 1, TypePercent, "Cave volume", &gd_sound_chunks_volume_percent },
592 { 1, TypeBoolean, "Classic sounds only", &gd_classic_sound },
593 { 1, TypeBoolean, "16-bit mixing", &gd_sdl_16bit_mixing },
594 { 1, TypeBoolean, "44kHz mixing", &gd_sdl_44khz_mixing },
595 #endif
596 { 1, TypeBoolean, "Use BDCFF highscore", &gd_use_bdcff_highscore },
597 { 1, TypeBoolean, "Show caveset name at uncover", &gd_show_name_of_game },
598 { 1, TypeBoolean, "Show story", &gd_show_story },
599 { 1, TypeStringv, "Status bar colors", &gd_status_bar_type, gd_status_bar_type_get_names() },
600 { 1, TypeBoolean, "All caves selectable", &gd_all_caves_selectable },
601 { 1, TypeBoolean, "Import as all selectable", &gd_import_as_all_caves_selectable },
602 { 1, TypeBoolean, "No invisible outbox", &gd_no_invisible_outbox },
603 { 1, TypeBoolean, "Random colors", &gd_random_colors },
605 { 2, TypeKey, "Key for left", &gd_sdl_key_left },
606 { 2, TypeKey, "Key for right", &gd_sdl_key_right },
607 { 2, TypeKey, "Key for up", &gd_sdl_key_up },
608 { 2, TypeKey, "Key for down", &gd_sdl_key_down },
609 { 2, TypeKey, "Key for fire", &gd_sdl_key_fire_1 },
610 { 2, TypeKey, "Key for fire (alt.)", &gd_sdl_key_fire_2 },
611 { 2, TypeKey, "Key for suicide", &gd_sdl_key_suicide },
613 const int numpages=settings[G_N_ELEMENTS(settings)-1].page+1; /* take it from last element of settings[] */
614 int n, page;
615 int current;
616 int y1[numpages], yd;
618 /* optionally create the list of themes, and also find the current one in the list. */
619 themes=gd_create_themes_list();
620 themenum=-1;
621 for (n=0; n<themes->len; n++)
622 if (strcmp0(gd_sdl_theme, g_ptr_array_index(themes, n))==0)
623 themenum=n;
624 if (themenum==-1) {
625 g_warning("theme %s not found in array", gd_sdl_theme);
626 themenum=0;
629 /* check pages, and x1,y1 coordinates for each */
630 yd=gd_font_height()+2;
631 for (page=0; page<numpages; page++) {
632 int num;
634 num=0;
635 for (n=0; n<G_N_ELEMENTS(settings); n++)
636 if (settings[n].page==page)
637 num++;
638 y1[page]=(gd_screen->h-num*yd)/2;
641 gd_backup_and_dark_screen();
642 gd_status_line("CRSR: MOVE SPACE: CHANGE ESC: EXIT");
643 gd_blittext_n(gd_screen, -1, gd_screen->h-3*gd_line_height(), GD_GDASH_GRAY1, "Some changes require restart.");
644 gd_blittext_n(gd_screen, -1, gd_screen->h-2*gd_line_height(), GD_GDASH_GRAY1, "Use T in the title for a new theme.");
646 current=0;
647 finished=FALSE;
648 while (!finished && !gd_quit) {
649 int linenum;
650 SDL_Event event;
652 page=settings[current].page; /* take the current page number from the current setting line */
653 gd_clear_line(gd_screen, 0); /* clear for title line */
654 gd_title_line("GDASH OPTIONS, PAGE %d/%d", page+1, numpages);
656 /* show settings */
657 linenum=0;
658 for (n=0; n<G_N_ELEMENTS(settings); n++) {
659 if (settings[n].page==page) {
660 const GdColor c_name=GD_GDASH_LIGHTBLUE;
661 const GdColor c_selected=GD_GDASH_YELLOW;
662 const GdColor c_value=GD_GDASH_GREEN;
664 int x, y;
666 x=4*gd_font_width();
667 y=y1[page]+linenum*yd;
668 x=gd_blittext_n(gd_screen, x, y, current==n?c_selected:c_name, settings[n].name);
669 x+=2*gd_font_width();
670 switch(settings[n].type) {
671 case TypeBoolean:
672 x=gd_blittext_n(gd_screen, x, y, c_value, *(gboolean *)settings[n].var?yes:no);
673 break;
674 case TypeScale:
675 x=gd_blittext_n(gd_screen, x, y, c_value, gd_scaling_name[*(GdScalingType *)settings[n].var]);
676 break;
677 case TypePercent:
678 x=gd_blittext_printf_n(gd_screen, x, y, c_value, "%d%%", *(int *)settings[n].var);
679 break;
680 case TypeTheme:
681 if (themenum==0)
682 x=gd_blittext_n(gd_screen, x, y, c_value, "[Default]");
683 else {
684 char *thm;
685 thm=g_filename_display_basename(g_ptr_array_index(themes, themenum));
686 if (strrchr(thm, '.')) /* remove extension */
687 *strrchr(thm, '.')='\0';
688 x=gd_blittext_n(gd_screen, x, y, c_value, thm);
689 g_free(thm);
691 break;
692 case TypeStringv:
693 x=gd_blittext_n(gd_screen, x, y, c_value, settings[n].stringv[*(int *)settings[n].var]);
694 break;
695 case TypeKey:
696 x=gd_blittext_n(gd_screen, x, y, c_value, gd_key_name(*(guint *)settings[n].var));
697 break;
700 linenum++;
703 SDL_Flip(gd_screen);
705 /* we don't leave text on the screen after us. */
706 /* so next iteration will have a nice empty screen to draw on :) */
707 linenum=0;
708 for (n=0; n<G_N_ELEMENTS(settings); n++) {
709 if (settings[n].page==page) {
710 gd_clear_line(gd_screen, y1[page]+linenum*yd);
711 linenum++;
715 SDL_WaitEvent(NULL);
716 while (SDL_PollEvent(&event)) {
717 switch(event.type) {
718 case SDL_QUIT:
719 gd_quit=TRUE;
720 break;
722 case SDL_KEYDOWN:
723 switch(event.key.keysym.sym) {
724 /* MOVEMENT */
725 case SDLK_UP: /* key up */
726 current=gd_clamp(current-1, 0, G_N_ELEMENTS(settings)-1);
727 break;
728 case SDLK_DOWN: /* key down */
729 current=gd_clamp(current+1, 0, G_N_ELEMENTS(settings)-1);
730 break;
731 case SDLK_PAGEUP:
732 if (page>0)
733 while (settings[current].page==page)
734 current--; /* decrement until previous page is found */
735 break;
736 case SDLK_PAGEDOWN:
737 if (page<numpages-1)
738 while (settings[current].page==page)
739 current++; /* increment until previous page is found */
740 break;
742 /* CHANGE SETTINGS */
743 case SDLK_LEFT: /* key left */
744 switch(settings[current].type) {
745 case TypeBoolean:
746 *(gboolean *)settings[current].var=FALSE;
747 break;
748 case TypeScale:
749 *(int *)settings[current].var=MAX(0,(*(int *)settings[current].var)-1);
750 break;
751 case TypePercent:
752 *(int *)settings[current].var=MAX(0,(*(int *)settings[current].var)-5);
753 break;
754 case TypeTheme:
755 themenum=gd_clamp(themenum-1, 0, themes->len-1);
756 break;
757 case TypeStringv:
758 *(int *)settings[current].var=gd_clamp(*(int *)settings[current].var-1, 0, g_strv_length((char **) settings[current].stringv)-1);
759 break;
760 case TypeKey:
761 break;
763 break;
765 case SDLK_RIGHT: /* key right */
766 switch(settings[current].type) {
767 case TypeBoolean:
768 *(gboolean *)settings[current].var=TRUE;
769 break;
770 case TypeScale:
771 *(int *)settings[current].var=MIN(GD_SCALING_MAX-1,(*(int *)settings[current].var)+1);
772 break;
773 case TypePercent:
774 *(int *)settings[current].var=MIN(100,(*(int *)settings[current].var)+5);
775 break;
776 case TypeTheme:
777 themenum=gd_clamp(themenum+1, 0, themes->len-1);
778 break;
779 case TypeStringv:
780 *(int *)settings[current].var=gd_clamp(*(int *)settings[current].var+1, 0, g_strv_length((char **) settings[current].stringv)-1);
781 break;
782 case TypeKey:
783 break;
785 break;
787 case SDLK_SPACE: /* key left */
788 case SDLK_RETURN:
789 switch (settings[current].type) {
790 case TypeBoolean:
791 *(gboolean *)settings[current].var=!*(gboolean *)settings[current].var;
792 break;
793 case TypeScale:
794 *(int *)settings[current].var=(*(int *)settings[current].var+1)%GD_SCALING_MAX;
795 break;
796 case TypePercent:
797 *(int *)settings[current].var=CLAMP((*(int *)settings[current].var+5), 0, 100);
798 break;
799 case TypeTheme:
800 themenum=(themenum+1)%themes->len;
801 break;
802 case TypeStringv:
803 *(int *)settings[current].var=(*(int *)settings[current].var+1)%g_strv_length((char **) settings[current].stringv);
804 break;
805 case TypeKey:
807 int i;
809 i=gd_select_key(settings[current].name);
810 if (i>0)
811 *(guint *)settings[current].var=i;
813 break;
815 break;
817 case SDLK_ESCAPE: /* finished options menu */
818 finished=TRUE;
819 break;
821 default:
822 /* other keys do nothing */
823 break;
824 } /* switch keypress key */
825 } /* switch event type */
826 } /* while pollevent */
829 /* set the theme. other variables are already set by the above code. */
830 g_free(gd_sdl_theme);
831 gd_sdl_theme=NULL;
832 if (themenum!=0)
833 gd_sdl_theme=g_strdup(g_ptr_array_index(themes, themenum));
834 gd_load_theme(); /* this loads the theme given in the global variable gd_sdl_theme. */
836 /* forget list of themes */
837 g_ptr_array_foreach(themes, (GFunc) g_free, NULL);
838 g_ptr_array_free(themes, TRUE);
840 gd_restore_screen();
842 #ifdef GD_SOUND
843 gd_sound_set_music_volume(gd_sound_music_volume_percent);
844 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
845 #endif
850 void
851 gd_install_theme()
853 char *filename;
855 filename=gd_select_file("SELECT IMAGE IMAGE FOR THEME", g_get_home_dir(), "*.bmp;*.png", FALSE);
856 if (filename) {
857 gd_clear_errors();
858 if (is_image_ok_for_theme(filename)) {
859 /* if file is said to be ok as a theme */
860 char *basename, *new_filename;
861 GError *error=NULL;
862 gchar *contents;
863 gsize length;
865 /* make up new filename */
866 basename=g_path_get_basename(filename);
867 new_filename=g_build_path(G_DIR_SEPARATOR_S, gd_user_config_dir, basename, NULL);
868 g_free(basename);
870 /* copy theme to user config directory */
871 if (g_file_get_contents(filename, &contents, &length, &error) && g_file_set_contents(new_filename, contents, length, &error)) {
872 /* copied file. */
874 else {
875 /* unable to copy file. */
876 g_warning("%s", error->message);
878 } else {
879 /* if file is not ok as a theme */
880 g_warning("%s cannot be used as a theme", filename);
883 g_free(filename);
886 void
887 gd_show_highscore(GdCave *highlight_cave, int highlight_line)
889 GdCave *cave;
890 const int screen_height=gd_screen->h/gd_line_height();
891 int max=MIN(G_N_ELEMENTS(cave->highscore), screen_height-3);
892 gboolean finished;
893 GList *current; /* current cave to view */
895 if (highlight_cave)
896 current=g_list_find(gd_caveset, highlight_cave);
898 gd_backup_and_dark_screen();
899 gd_title_line("THE HALL OF FAME");
900 gd_status_line("CRSR: CAVE SPACE: EXIT");
902 current=NULL;
903 finished=FALSE;
904 while (!finished && !gd_quit) {
905 int i;
906 GdHighScore *scores;
907 SDL_Event event;
909 /* current cave or game */
910 if (current)
911 cave=current->data;
912 else
913 cave=NULL;
914 if (!cave)
915 scores=gd_caveset_data->highscore;
916 else
917 scores=cave->highscore;
919 gd_clear_line(gd_screen, gd_font_height());
920 gd_blittext_n(gd_screen, -1, gd_font_height(), GD_GDASH_YELLOW, cave?cave->name:gd_caveset_data->name);
923 /* show scores */
924 for (i=0; i<max; i++) {
925 int c;
927 gd_clear_line(gd_screen, (i+2)*gd_line_height());
928 c=i/5%2?GD_GDASH_PURPLE:GD_GDASH_GREEN;
929 if (cave==highlight_cave && i==highlight_line)
930 c=GD_GDASH_WHITE;
931 if (scores[i].score!=0)
932 gd_blittext_printf_n(gd_screen, 0, (i+2)*gd_line_height(), c, "%2d %6d %s", i+1, scores[i].score, scores[i].name);
934 SDL_Flip(gd_screen);
936 /* process events */
937 SDL_WaitEvent(NULL);
938 while (SDL_PollEvent(&event)) {
939 switch (event.type) {
940 case SDL_QUIT:
941 gd_quit=TRUE;
942 break;
944 case SDL_KEYDOWN:
945 switch (event.key.keysym.sym) {
946 /* movements */
947 case SDLK_LEFT:
948 case SDLK_UP:
949 case SDLK_PAGEUP:
950 if (current!=NULL)
951 current=current->prev;
952 break;
953 case SDLK_RIGHT:
954 case SDLK_DOWN:
955 case SDLK_PAGEDOWN:
956 if (current!=NULL) {
957 /* if showing a cave, go to next cave (if any) */
958 if (current->next!=NULL)
959 current=current->next;
961 else {
962 /* if showing game, show first cave. */
963 current=gd_caveset;
965 break;
966 case SDLK_SPACE:
967 case SDLK_ESCAPE:
968 case SDLK_RETURN:
969 finished=TRUE;
970 break;
971 default:
972 /* other keys do nothing */
973 break;
975 default:
976 /* other events do nothing */
977 break;
982 gd_restore_screen();
987 void
988 gd_show_cave_info(GdCave *show_cave)
990 GdCave *cave;
991 const int screen_height=gd_screen->h/gd_font_height();
992 gboolean finished;
993 GList *current=NULL; /* current cave to view */
995 if (show_cave)
996 current=g_list_find(gd_caveset, show_cave);
997 gd_backup_and_dark_screen();
998 gd_title_line("CAVESET INFORMATION");
999 gd_status_line("LEFT, RIGHT: CAVE SPACE: EXIT");
1001 finished=FALSE;
1002 while (!finished && !gd_quit) {
1003 GString *text;
1004 int i;
1005 int y;
1006 char *wrapped;
1007 SDL_Event event;
1009 /* current cave or game */
1010 if (current)
1011 cave=current->data;
1012 else
1013 cave=NULL;
1015 for (i=1; i<screen_height-1; i++)
1016 gd_clear_line(gd_screen, i*gd_font_height());
1018 gd_blittext_n(gd_screen, -1, gd_font_height(), GD_GDASH_YELLOW, cave?cave->name:gd_caveset_data->name);
1019 y=gd_font_height()+2*gd_line_height();
1021 /* show data */
1022 text=g_string_new(NULL);
1023 if (!cave) {
1024 /* ... FOR CAVESET */
1025 /* ... FOR CAVE */
1026 if (!g_str_equal(gd_caveset_data->author, "")) {
1027 g_string_append_printf(text, "%s", gd_caveset_data->author);
1028 if (!g_str_equal(gd_caveset_data->date, ""))
1029 g_string_append_printf(text, ", %s", gd_caveset_data->date);
1030 g_string_append_c(text, '\n');
1033 if (!g_str_equal(gd_caveset_data->description, ""))
1034 g_string_append_printf(text, "%s\n", gd_caveset_data->description);
1036 if (gd_caveset_data->story->len>0) {
1037 g_string_append(text, gd_caveset_data->story->str);
1038 /* if the cave story has no enter on the end, add one. */
1039 if (text->str[text->len-1]!='\n')
1040 g_string_append_c(text, '\n');
1042 if (gd_caveset_data->remark->len>0) {
1043 g_string_append(text, gd_caveset_data->remark->str);
1044 /* if the cave story has no enter on the end, add one. */
1045 if (text->str[text->len-1]!='\n')
1046 g_string_append_c(text, '\n');
1048 } else {
1049 /* ... FOR CAVE */
1050 if (!g_str_equal(cave->author, "")) {
1051 g_string_append_printf(text, "%s", cave->author);
1052 if (!g_str_equal(cave->date, ""))
1053 g_string_append_printf(text, ", %s", cave->date);
1054 g_string_append_c(text, '\n');
1057 if (!g_str_equal(cave->description, ""))
1058 g_string_append_printf(text, "%s\n", cave->description);
1060 if (cave->story->len>0) {
1061 g_string_append(text, cave->story->str);
1062 /* if the cave story has no enter on the end, add one. */
1063 if (text->str[text->len-1]!='\n')
1064 g_string_append_c(text, '\n');
1066 if (cave->remark->len>0) {
1067 g_string_append(text, cave->remark->str);
1068 /* if the cave story has no enter on the end, add one. */
1069 if (text->str[text->len-1]!='\n')
1070 g_string_append_c(text, '\n');
1073 wrapped=gd_wrap_text(text->str, gd_screen->w/gd_font_width()-2);
1074 gd_blittext_n(gd_screen, gd_font_width(), gd_line_height()*2, GD_GDASH_LIGHTBLUE, wrapped);
1075 g_free(wrapped);
1076 g_string_free(text, TRUE);
1077 SDL_Flip(gd_screen);
1079 /* process events */
1080 SDL_WaitEvent(NULL);
1081 while (SDL_PollEvent(&event)) {
1082 switch (event.type) {
1083 case SDL_QUIT:
1084 gd_quit=TRUE;
1085 break;
1087 case SDL_KEYDOWN:
1088 switch (event.key.keysym.sym) {
1089 /* movements */
1090 case SDLK_LEFT:
1091 case SDLK_UP:
1092 case SDLK_PAGEUP:
1093 if (current!=NULL)
1094 current=current->prev;
1095 break;
1096 case SDLK_RIGHT:
1097 case SDLK_DOWN:
1098 case SDLK_PAGEDOWN:
1099 if (current!=NULL) {
1100 /* if showing a cave, go to next cave (if any) */
1101 if (current->next!=NULL)
1102 current=current->next;
1104 else {
1105 /* if showing game, show first cave. */
1106 current=gd_caveset;
1108 break;
1109 case SDLK_SPACE:
1110 case SDLK_ESCAPE:
1111 case SDLK_RETURN:
1112 finished=TRUE;
1113 break;
1114 default:
1115 /* other keys do nothing */
1116 break;
1118 default:
1119 /* other events do nothing */
1120 break;
1125 gd_restore_screen();
1130 static void
1131 wait_for_keypress()
1133 gboolean stop;
1135 stop=FALSE;
1136 while (!gd_quit && !stop) {
1137 SDL_Event event;
1139 while(SDL_PollEvent(&event)) {
1140 switch(event.type) {
1141 case SDL_QUIT:
1142 gd_quit=TRUE;
1143 break;
1145 case SDL_KEYDOWN:
1146 stop=TRUE;
1147 break;
1151 if (gd_fire())
1152 stop=TRUE;
1153 SDL_Delay(100);
1156 gd_wait_for_key_releases();
1163 void
1164 gd_help(const char **strings)
1166 int y, n;
1167 int numstrings;
1168 int charwidth, x1;
1170 /* remember screen contents */
1171 gd_backup_and_dark_screen();
1173 gd_title_line("GDASH HELP");
1174 gd_status_line("SPACE: EXIT");
1176 numstrings=g_strv_length((gchar **) strings);
1178 charwidth=0;
1179 for (n=0; n<numstrings; n+=2)
1180 charwidth=MAX(charwidth, strlen(strings[n])+1+strlen(strings[n+1]));
1181 x1=gd_screen->w/2-charwidth*gd_font_width()/2;
1183 y=(gd_screen->h-numstrings*(gd_line_height())/2)/2;
1184 for (n=0; n<numstrings; n+=2) {
1185 int x;
1187 x=gd_blittext_printf_n(gd_screen, x1, y, GD_GDASH_YELLOW, "%s ", strings[n]);
1188 x=gd_blittext_printf_n(gd_screen, x, y, GD_GDASH_LIGHTBLUE, "%s", strings[n+1]);
1190 y+=gd_line_height();
1193 SDL_Flip(gd_screen);
1195 wait_for_keypress();
1197 /* copy screen contents back */
1198 gd_restore_screen();
1201 static void
1202 draw_window(SDL_Rect *rect)
1204 rect->x-=2;
1205 rect->y-=2;
1206 rect->w+=4;
1207 rect->h+=4;
1208 SDL_FillRect(gd_screen, rect, SDL_MapRGB(gd_screen->format, 64, 64, 64));
1209 rect->x++;
1210 rect->y++;
1211 rect->w-=2;
1212 rect->h-=2;
1213 SDL_FillRect(gd_screen, rect, SDL_MapRGB(gd_screen->format, 128, 128, 128));
1214 rect->x++;
1215 rect->y++;
1216 rect->w-=2;
1217 rect->h-=2;
1218 SDL_FillRect(gd_screen, rect, SDL_MapRGB(gd_screen->format, 0, 0, 0));
1222 void
1223 gd_message(const char *message)
1225 SDL_Rect rect;
1226 int height, lines;
1227 int y1;
1228 char *wrapped;
1229 int x;
1231 wrapped=gd_wrap_text(message, 38);
1232 /* wrapped always has a \n on the end, so this returns at least 2 */
1233 lines=gd_lines_in_text(wrapped);
1235 height=(1+lines)*gd_font_height();
1236 gd_backup_and_dark_screen();
1237 gd_status_line("SPACE: CONTINUE");
1239 y1=(gd_screen->h-height)/2;
1240 rect.x=gd_font_width();
1241 rect.w=gd_screen->w-2*gd_font_width();
1242 rect.y=y1;
1243 rect.h=height;
1244 draw_window(&rect);
1245 SDL_SetClipRect(gd_screen, &rect);
1247 /* lines is at least 2. so 3 and above means multiple lines */
1248 if (lines>2)
1249 x=rect.x;
1250 else
1251 x=-1;
1253 gd_blittext_n(gd_screen, x, y1+gd_font_height(), GD_GDASH_WHITE, wrapped);
1254 SDL_Flip(gd_screen);
1255 wait_for_keypress();
1257 SDL_SetClipRect(gd_screen, NULL);
1258 gd_restore_screen();
1259 g_free(wrapped);
1262 char *
1263 gd_input_string(const char *title, const char *current)
1265 int y1;
1266 SDL_Rect rect;
1267 gboolean enter, escape;
1268 GString *text;
1269 int n;
1270 int height, width;
1272 gd_backup_and_black_screen();
1274 height=6*gd_line_height();
1275 y1=(gd_screen->h-height)/2; /* middle of the screen */
1276 rect.x=8;
1277 rect.y=y1;
1278 rect.w=gd_screen->w-2*8;
1279 rect.h=height;
1280 draw_window(&rect);
1281 SDL_SetClipRect(gd_screen, &rect);
1283 width=rect.w/gd_font_width();
1285 gd_blittext_n(gd_screen, -1, y1+gd_line_height(), GD_GDASH_WHITE, title);
1287 text=g_string_new(current);
1289 /* setup keyboard */
1290 SDL_EnableUNICODE(1);
1292 enter=escape=FALSE;
1293 n=0;
1294 while (!gd_quit && !enter && !escape) {
1295 SDL_Event event;
1296 int len, x;
1298 n=(n+1)%10; /* for blinking cursor */
1299 gd_clear_line(gd_screen, y1+3*gd_line_height());
1300 len=g_utf8_strlen(text->str, -1);
1301 if (len<width-1)
1302 x=-1; /* if fits on screen (+1 for cursor), centered */
1303 else
1304 x=rect.x+rect.w-(len+1)*gd_font_width(); /* otherwise show end, +1 for cursor */
1306 gd_blittext_printf_n(gd_screen, x, y1+3*gd_line_height(), GD_GDASH_WHITE, "%s%c", text->str, n>=5?'_':' ');
1307 SDL_Flip(gd_screen);
1309 while(SDL_PollEvent(&event))
1310 switch(event.type) {
1311 case SDL_QUIT:
1312 gd_quit=TRUE;
1313 break;
1315 case SDL_KEYDOWN:
1316 if (event.key.keysym.sym==SDLK_RETURN)
1317 enter=TRUE;
1318 else
1319 if (event.key.keysym.sym==SDLK_ESCAPE)
1320 escape=TRUE;
1321 else
1322 if (event.key.keysym.sym==SDLK_BACKSPACE || event.key.keysym.sym==SDLK_DELETE) {
1323 /* delete one character from the end */
1324 if (text->len!=0) {
1325 char *ptr=text->str+text->len; /* string pointer + length: points to the terminating zero */
1327 ptr=g_utf8_prev_char(ptr); /* step back one utf8 character */
1328 g_string_truncate(text, ptr-text->str);
1331 else
1332 if (event.key.keysym.unicode!=0)
1333 g_string_append_unichar(text, event.key.keysym.unicode);
1334 break;
1337 SDL_Delay(100);
1340 /* forget special keyboard settings we needed here */
1341 SDL_EnableUNICODE(0);
1342 /* restore screen */
1343 SDL_SetClipRect(gd_screen, NULL);
1344 gd_restore_screen();
1346 gd_wait_for_key_releases();
1348 /* if quit, return nothing. */
1349 if (gd_quit)
1350 return NULL;
1352 if (enter)
1353 return g_string_free(text, FALSE);
1354 /* here must be escape=TRUE, we return NULL as no string is really entered */
1355 g_string_free(text, TRUE);
1356 return NULL;
1359 /* select a keysim for some action. returns -1 if error, or the keysym. */
1361 gd_select_key(const char *title)
1363 const int height=5*gd_line_height();
1364 int y1;
1365 SDL_Rect rect;
1366 gboolean got_key;
1367 guint key=0; /* default value to avoid compiler warning */
1369 y1=(gd_screen->h-height)/2;
1370 rect.x=gd_font_width();
1371 rect.w=gd_screen->w-2*gd_font_width();
1372 rect.y=y1;
1373 rect.h=height;
1374 gd_backup_and_black_screen();
1375 draw_window(&rect);
1376 gd_blittext_n(gd_screen, -1, y1+gd_line_height(), GD_GDASH_WHITE, title);
1377 gd_blittext_n(gd_screen, -1, y1+3*gd_line_height(), GD_GDASH_GRAY2, "Press desired key for action!");
1378 SDL_Flip(gd_screen);
1380 got_key=FALSE;
1381 while (!gd_quit && !got_key) {
1382 SDL_Event event;
1384 SDL_WaitEvent(&event);
1385 switch(event.type) {
1386 case SDL_QUIT:
1387 gd_quit=TRUE;
1388 break;
1390 case SDL_KEYDOWN:
1391 key=event.key.keysym.sym;
1392 got_key=TRUE;
1393 break;
1395 default:
1396 /* other events not interesting */
1397 break;
1401 /* restore screen */
1402 gd_restore_screen();
1404 gd_wait_for_key_releases();
1406 if (got_key)
1407 return key;
1408 return -1;
1413 void
1414 gd_error_console()
1416 GList *iter;
1417 GPtrArray *err;
1418 const int yd=gd_line_height();
1419 const int names_per_page=gd_screen->h/yd-3;
1420 gboolean exit, clear;
1421 int sel;
1422 gboolean redraw;
1424 if (!gd_errors) {
1425 gd_message("No error messages.");
1426 return;
1429 err=g_ptr_array_new();
1430 for (iter=gd_errors; iter!=NULL; iter=iter->next)
1431 g_ptr_array_add(err, iter->data);
1433 /* the user has seen the errors, clear the "has new error" flag */
1434 gd_clear_error_flag();
1436 gd_backup_and_dark_screen();
1437 gd_title_line("GDASH ERRORS");
1438 gd_status_line("CRSR: SELECT C: CLEAR ESC: EXIT");
1440 exit=FALSE;
1441 clear=FALSE;
1443 /* show errors */
1444 sel=0;
1445 redraw=TRUE;
1446 while (!gd_quit && !exit && !clear) {
1447 int page, i, cur;
1449 page=sel/names_per_page;
1451 if (redraw) {
1452 if (err->len!=0) {
1453 for (i=0, cur=page*names_per_page; i<names_per_page; i++, cur++) {
1454 GdErrorMessage *m=g_ptr_array_index(err, cur);
1455 int col;
1457 col=cur==sel?GD_GDASH_YELLOW:GD_GDASH_LIGHTBLUE;
1459 gd_clear_line(gd_screen, (i+2)*yd);
1460 if (cur<err->len) /* may not be as much filenames as it would fit on the screen */
1461 gd_blittext_n(gd_screen, 0, (i+2)*yd, col, m->message);
1464 SDL_Flip(gd_screen);
1466 redraw=FALSE;
1469 gd_process_pending_events();
1471 /* cursor movement */
1472 if (gd_up())
1473 sel=gd_clamp(sel-1, 0, err->len-1), redraw=TRUE;
1474 if (gd_down())
1475 sel=gd_clamp(sel+1, 0, err->len-1), redraw=TRUE;
1476 if (gd_keystate[SDLK_PAGEUP])
1477 sel=gd_clamp(sel-names_per_page, 0, err->len-1), redraw=TRUE;
1478 if (gd_keystate[SDLK_PAGEDOWN])
1479 sel=gd_clamp(sel+names_per_page, 0, err->len-1), redraw=TRUE;
1480 if (gd_keystate[SDLK_HOME])
1481 sel=0, redraw=TRUE;
1482 if (gd_keystate[SDLK_END])
1483 sel=err->len-1, redraw=TRUE;
1485 if (gd_fire() || gd_keystate[SDLK_RETURN] || gd_keystate[SDLK_SPACE])
1486 /* show one error */
1487 if (err->len!=0) {
1488 gd_wait_for_key_releases();
1489 gd_show_error(g_ptr_array_index(err, sel));
1492 if (gd_keystate[SDLK_c])
1493 clear=TRUE;
1495 if (gd_keystate[SDLK_ESCAPE])
1496 exit=TRUE;
1498 SDL_Delay(100);
1501 /* wait until the user releases return and escape, as it might be passed to the caller accidentally */
1502 /* also wait because we do not want to process one enter keypress more than once */
1503 gd_wait_for_key_releases();
1505 g_ptr_array_free(err, TRUE);
1507 if (clear)
1508 gd_clear_errors();
1510 gd_restore_screen();
1514 gboolean
1515 gd_ask_yes_no(const char *question, const char *answer1, const char *answer2, gboolean *result)
1517 int height;
1518 int y1;
1519 SDL_Rect rect;
1520 gboolean success, escape;
1521 int n;
1523 gd_backup_and_black_screen();
1524 height=5*gd_line_height();
1525 y1=(gd_screen->h-height)/2; /* middle of the screen */
1526 rect.x=8;
1527 rect.y=y1;
1528 rect.w=gd_screen->w-2*8;
1529 rect.h=height;
1530 draw_window(&rect);
1531 SDL_SetClipRect(gd_screen, &rect);
1533 gd_blittext_n(gd_screen, -1, y1+gd_line_height(), GD_GDASH_WHITE, question);
1534 gd_blittext_printf_n(gd_screen, -1, y1+3*gd_line_height(), GD_GDASH_WHITE, "N: %s, Y: %s", answer1, answer2);
1535 SDL_Flip(gd_screen);
1537 success=escape=FALSE;
1538 n=0;
1539 while (!gd_quit && !success && !escape) {
1540 SDL_Event event;
1542 n=(n+1)%10;
1544 while(SDL_PollEvent(&event))
1545 switch(event.type) {
1546 case SDL_QUIT:
1547 gd_quit=TRUE;
1548 break;
1550 case SDL_KEYDOWN:
1551 if (event.key.keysym.sym==SDLK_y) { /* user pressed yes */
1552 *result=TRUE;
1553 success=TRUE;
1555 else
1556 if (event.key.keysym.sym==SDLK_n) { /* user pressed no */
1557 *result=FALSE;
1558 success=TRUE;
1560 else
1561 if (event.key.keysym.sym==SDLK_ESCAPE) /* user pressed escape */
1562 escape=TRUE;
1563 break;
1566 SDL_Delay(100);
1568 /* restore screen */
1569 gd_restore_screen();
1571 gd_wait_for_key_releases();
1573 SDL_SetClipRect(gd_screen, NULL);
1575 /* this will return true, if y or n pressed. returns false, if escape, or quit event */
1576 return success;
1580 gboolean
1581 gd_discard_changes()
1583 gboolean answered, result;
1585 /* if not edited, simply answer yes. */
1586 if (!gd_caveset_edited)
1587 return TRUE;
1589 /* if the caveset is edited, ask the user if to save. */
1590 answered=gd_ask_yes_no("New replays are added. Discard them?", "Cancel", "Discard", &result);
1591 if (!answered || !result)
1592 /* if does not want to discard, say false */
1593 return FALSE;
1595 return TRUE;
1600 void
1601 gd_show_license()
1603 char *wrapped;
1605 /* remember screen contents */
1606 gd_backup_and_dark_screen();
1608 gd_title_line("GDASH LICENSE");
1609 gd_status_line("SPACE: EXIT");
1611 wrapped=gd_wrap_text(gd_about_license, gd_screen->w/gd_font_width());
1612 gd_blittext_n(gd_screen, 0, gd_line_height(), GD_GDASH_LIGHTBLUE, wrapped);
1613 g_free(wrapped);
1614 SDL_Flip(gd_screen);
1616 wait_for_keypress();
1618 /* copy screen contents back */
1619 gd_restore_screen();
1624 static int
1625 help_writeattrib(int x, int y, const char *name, const char *content)
1627 const int yd=gd_line_height();
1629 gd_blittext_n(gd_screen, x, y, GD_GDASH_YELLOW, name);
1630 gd_blittext_n(gd_screen, x+10, y+yd, GD_GDASH_LIGHTBLUE, content);
1631 if (strchr(content, '\n'))
1632 y+=yd;
1634 return y+2*yd+yd/2;
1637 static int
1638 help_writeattribs(int x, int y, const char *name, const char *content[])
1640 const int yd=gd_line_height();
1642 if (content!=NULL && content[0]!=NULL) {
1643 int i;
1645 gd_blittext_n(gd_screen, x, y, GD_GDASH_YELLOW, name);
1647 y+=yd;
1648 for (i=0; content[i]!=NULL; i++) {
1649 gd_blittext_n(gd_screen, x+10, y, GD_GDASH_LIGHTBLUE, content[i]);
1651 y+=yd;
1655 return y+yd/2;
1659 void
1660 gd_about()
1662 int y;
1664 /* remember screen contents */
1665 gd_backup_and_dark_screen();
1667 gd_title_line("GDASH " PACKAGE_VERSION);
1668 gd_status_line("SPACE: EXIT");
1670 y=10;
1672 y=help_writeattrib(-1, y, "", gd_about_comments);
1673 y=help_writeattrib(10, y, "WEBSITE", gd_about_website);
1674 y=help_writeattribs(10, y, "AUTHORS", gd_about_authors);
1675 y=help_writeattribs(10, y, "ARTISTS", gd_about_artists);
1676 y=help_writeattribs(10, y, "DOCUMENTERS", gd_about_documenters);
1677 /* extern char *gd_about_translator_credits; - NO TRANSLATION IN SDASH */
1678 SDL_Flip(gd_screen);
1680 wait_for_keypress();
1682 /* copy screen contents back */
1683 gd_restore_screen();
1687 void
1688 gd_show_error(GdErrorMessage *error)
1690 char *wrapped;
1691 char **lines;
1692 int linenum;
1693 int y1, i, yd;
1695 wrapped=gd_wrap_text(error->message, gd_screen->w/gd_font_width()-2);
1696 gd_backup_and_dark_screen();
1697 lines=g_strsplit_set(wrapped, "\n", -1);
1698 linenum=g_strv_length(lines);
1700 yd=gd_line_height();
1701 y1=gd_screen->h/2-(linenum+1)*yd/2;
1703 gd_title_line("GDASH ERROR");
1704 gd_status_line("ANY KEY: CONTINUE");
1705 for (i=0; lines[i]!=NULL; i++)
1706 gd_blittext_n(gd_screen, 8, y1+(i+1)*yd, GD_GDASH_WHITE, lines[i]);
1707 SDL_Flip(gd_screen);
1709 wait_for_keypress();
1711 gd_restore_screen();
1712 g_free(wrapped);
1713 g_strfreev(lines);
1717 static void
1718 show_comment(const char *text)
1720 char *wrapped;
1721 char **lines;
1722 int linenum;
1723 int y1, i, yd;
1725 wrapped=gd_wrap_text(text, gd_screen->w/gd_font_width()-2);
1726 gd_backup_and_dark_screen();
1727 lines=g_strsplit_set(wrapped, "\n", -1);
1728 linenum=g_strv_length(lines);
1730 yd=gd_line_height();
1731 y1=gd_screen->h/2-(linenum+1)*yd/2;
1733 gd_title_line("REPLAY COMMENT");
1734 gd_status_line("ANY KEY: CONTINUE");
1735 for (i=0; lines[i]!=NULL; i++)
1736 gd_blittext_n(gd_screen, 8, y1+(i+1)*yd, GD_GDASH_WHITE, lines[i]);
1737 SDL_Flip(gd_screen);
1739 wait_for_keypress();
1741 gd_restore_screen();
1742 g_free(wrapped);
1743 g_strfreev(lines);
1749 * SDASH REPLAYS MENU
1751 void
1752 gd_replays_menu(void (*play_func) (GdCave *cave, GdReplay *replay), gboolean for_game)
1754 gboolean finished;
1755 /* an item stores a cave (to see its name) or a cave+replay */
1756 typedef struct _item {
1757 GdCave *cave;
1758 GdReplay *replay;
1759 } Item;
1760 int n, page;
1761 int current;
1762 const int lines_per_page=gd_screen->h/gd_line_height()-5;
1763 GList *citer;
1764 GPtrArray *items=NULL;
1765 GdCave *cave;
1766 Item i;
1768 items=g_ptr_array_new();
1769 /* for all caves */
1770 for (citer=gd_caveset; citer!=NULL; citer=citer->next) {
1771 GList *riter;
1773 cave=citer->data;
1774 /* if cave has replays... */
1775 if (cave->replays!=NULL) {
1776 /* add cave data */
1777 i.cave=cave;
1778 i.replay=NULL;
1779 g_ptr_array_add(items, g_memdup(&i, sizeof(i)));
1781 /* add replays, too */
1782 for (riter=cave->replays; riter!=NULL; riter=riter->next) {
1783 i.replay=(GdReplay *)riter->data;
1784 g_ptr_array_add(items, g_memdup(&i, sizeof(i)));
1789 if (items->len==0) {
1790 gd_message("No replays.");
1791 } else {
1793 gd_backup_and_dark_screen();
1794 if (for_game)
1795 gd_status_line("CRSR:MOVE C:COMMENT S:SAVED ESC:EXIT");
1796 else
1797 gd_status_line("CRSR: MOVE SPACE: SAVE ESC: EXIT");
1799 current=1;
1800 finished=FALSE;
1801 while (!finished && !gd_quit) {
1802 page=current/lines_per_page; /* show 18 lines per page */
1803 SDL_Event event;
1805 /* show lines */
1806 gd_clear_line(gd_screen, 0); /* for empty top row */
1807 for (n=0; n<lines_per_page; n++) { /* for empty caves&replays rows */
1808 int y=(n+2)*gd_line_height();
1810 gd_clear_line(gd_screen, y);
1813 gd_title_line("GDASH REPLAYS, PAGE %d/%d", page+1, items->len/lines_per_page+1);
1814 for (n=0; n<lines_per_page && page*lines_per_page+n<items->len; n++) {
1815 int pos=page*lines_per_page+n;
1816 GdColor col_cave=current==pos?GD_GDASH_YELLOW:GD_GDASH_LIGHTBLUE; /* selected=yellow, otherwise blue */
1817 GdColor col=current==pos?GD_GDASH_YELLOW:GD_GDASH_GRAY3; /* selected=yellow, otherwise blue */
1818 Item *i;
1819 int x, y;
1821 i=(Item *) g_ptr_array_index(items, pos);
1823 x=0;
1824 y=(n+2)*gd_line_height();
1826 if (!i->replay) {
1827 /* no replay pointer: this is a cave, so write its name. */
1828 x=gd_blittext_n(gd_screen, x, y, col_cave, i->cave->name);
1829 } else {
1830 const char *comm;
1831 int c;
1832 char buffer[100];
1834 /* successful or not */
1835 x=gd_blittext_printf_n(gd_screen, x, y, i->replay->success?GD_GDASH_GREEN:GD_GDASH_RED, " %c ", GD_BALL_CHAR);
1837 /* player name */
1838 g_utf8_strncpy(buffer, i->replay->player_name, 20); /* name: maximum 20 characters */
1839 x=gd_blittext_n(gd_screen, x, y, col, buffer);
1840 /* put 20-length spaces */
1841 for (c=g_utf8_strlen(buffer, -1); c<20; c++)
1842 x=gd_blittext_n(gd_screen, x, y, col, " ");
1843 /* always put one space after name */
1844 x=gd_blittext_n(gd_screen, x, y, col, " ");
1846 /* write date */
1847 if (!g_str_equal(i->replay->date, ""))
1848 comm=i->replay->date;
1849 else
1850 /* or nothing */
1851 comm="-";
1852 g_utf8_strncpy(buffer, comm, 11); /* date: maximum 11 characters */
1853 x=gd_blittext_n(gd_screen, x, y, col, comm);
1854 /* put 20-length spaces */
1855 for (c=g_utf8_strlen(buffer, -1); c<11; c++)
1856 x=gd_blittext_n(gd_screen, x, y, col, " ");
1858 /* level */
1859 x=gd_blittext_printf_n(gd_screen, x, y, col, " %d", i->replay->level+1);
1860 /* comment */
1861 x=gd_blittext_printf_n(gd_screen, x, y, col, "%c", i->replay->comment->len!=0?GD_COMMENT_CHAR:' ');
1862 /* saved - check box */
1863 x=gd_blittext_printf_n(gd_screen, x, y, col, "%c", i->replay->saved?GD_CHECKED_BOX_CHAR:GD_UNCHECKED_BOX_CHAR);
1866 SDL_Flip(gd_screen); /* draw to usere's screen */
1868 SDL_WaitEvent(&event);
1869 switch (event.type) {
1870 case SDL_QUIT:
1871 gd_quit=TRUE;
1872 break;
1874 case SDL_KEYDOWN:
1875 switch (event.key.keysym.sym) {
1876 case SDLK_UP:
1877 do {
1878 current=gd_clamp(current-1, 1, items->len-1);
1879 } while (((Item *)g_ptr_array_index(items, current))->replay==NULL && current>=1);
1880 break;
1881 case SDLK_DOWN:
1882 do {
1883 current=gd_clamp(current+1, 1, items->len-1);
1884 } while (((Item *)g_ptr_array_index(items, current))->replay==NULL && current<items->len);
1885 break;
1886 case SDLK_c:
1887 /* show comment */
1889 Item *i=(Item *)g_ptr_array_index(items, current);
1891 if (i->replay && !g_str_equal(i->replay->comment->str, ""))
1892 show_comment(i->replay->comment->str);
1894 break;
1895 case SDLK_s:
1896 /* only allow toggling the "saved" flag, if we are in a game. not in the replay->video converter app. */
1897 if (for_game) {
1898 Item *i=(Item *)g_ptr_array_index(items, current);
1900 if (i->replay) {
1901 i->replay->saved=!i->replay->saved;
1902 gd_caveset_edited=TRUE;
1905 break;
1906 case SDLK_SPACE:
1907 case SDLK_RETURN:
1909 Item *i=(Item *)g_ptr_array_index(items, current);
1911 if (i->replay) {
1912 gd_backup_and_black_screen();
1913 SDL_Flip(gd_screen);
1914 play_func(i->cave, i->replay);
1915 gd_restore_screen();
1918 break;
1919 case SDLK_ESCAPE:
1920 finished=TRUE;
1921 break;
1923 case SDLK_PAGEUP:
1924 current=gd_clamp(current-lines_per_page, 0, items->len-1);
1925 break;
1927 case SDLK_PAGEDOWN:
1928 current=gd_clamp(current+lines_per_page, 0, items->len-1);
1929 break;
1931 default:
1932 /* other keys do nothing */
1933 break;
1935 break;
1937 default:
1938 /* other events do nothing */
1939 break;
1943 gd_restore_screen();
1946 /* set the theme. other variables are already set by the above code. */
1947 /* forget list of themes */
1948 g_ptr_array_foreach(items, (GFunc) g_free, NULL);
1949 g_ptr_array_free(items, TRUE);