PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/release-20051119/Source_Files/Misc/sdl_dialogs.cpp

#
C++ | 1387 lines | 934 code | 242 blank | 211 comment | 209 complexity | 39df8d78625ec9dc326909affb733eb5 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, BSD-3-Clause, GPL-3.0, LGPL-3.0, MPL-2.0-no-copyleft-exception, Zlib, GPL-2.0
  1. /*
  2. Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
  3. and the "Aleph One" developers.
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. This license is contained in the file "COPYING",
  13. which is included with this source code; it is available online at
  14. http://www.gnu.org/licenses/gpl.html
  15. */
  16. /*
  17. * sdl_dialogs.cpp - SDL implementation of user dialogs
  18. *
  19. * Written in 2000 by Christian Bauer
  20. *
  21. * 11 Mar 2002 (Woody Zenfell): renamed XML_FrameParser to XML_DFrameParser
  22. * to resolve conflict with new animated-model frame parsing code.
  23. *
  24. * 5 Feb 2003 (Woody Zenfell): exposed draw_dirty_widgets() functionality
  25. * also trying to fix fullscreen drawing problems related to flipping
  26. */
  27. #include "cseries.h"
  28. #include "sdl_dialogs.h"
  29. #include "sdl_fonts.h"
  30. #include "sdl_widgets.h"
  31. #include "shape_descriptors.h"
  32. #include "screen_drawing.h"
  33. #include "shell.h"
  34. #include "screen.h"
  35. #include "images.h"
  36. #include "world.h"
  37. #include "mysound.h"
  38. #include "game_errors.h"
  39. #include "XML_Loader_SDL.h"
  40. #include "XML_ParseTreeRoot.h"
  41. #include <map>
  42. #ifdef __MVCPP__
  43. #include <string>
  44. #endif
  45. // Global variables
  46. dialog *top_dialog = NULL;
  47. static SDL_Surface *dialog_surface = NULL;
  48. static sdl_font_info *default_font = NULL;
  49. static SDL_Surface *default_image = NULL;
  50. static OpenedResourceFile theme_resources;
  51. static TextSpec dialog_font_spec[NUM_DIALOG_FONTS];
  52. static sdl_font_info *dialog_font[NUM_DIALOG_FONTS];
  53. static SDL_Color dialog_color[NUM_DIALOG_COLORS];
  54. static uint16 dialog_space[NUM_DIALOG_SPACES];
  55. static struct dialog_image_spec_type {
  56. string name;
  57. bool scale;
  58. } dialog_image_spec[NUM_DIALOG_IMAGES];
  59. static SDL_Surface *dialog_image[NUM_DIALOG_IMAGES];
  60. // Prototypes
  61. static void shutdown_dialogs(void);
  62. static void unload_theme(void);
  63. static void set_theme_defaults(void);
  64. /*
  65. * Initialize dialog manager
  66. */
  67. void initialize_dialogs(FileSpecifier &theme)
  68. {
  69. // Allocate surface for dialogs (this surface is needed because when
  70. // OpenGL is active, we can't write directly to the screen)
  71. dialog_surface = SDL_CreateRGBSurface(SDL_SWSURFACE, 640, 480, 16, 0x7c00, 0x03e0, 0x001f, 0);
  72. assert(dialog_surface);
  73. // Default font and image
  74. static const TextSpec default_font_spec = {kFontIDMonaco, styleNormal, 12};
  75. default_font = load_font(default_font_spec);
  76. assert(default_font);
  77. default_image = SDL_CreateRGBSurface(SDL_SWSURFACE, 1, 1, 24, 0xff0000, 0x00ff00, 0x0000ff, 0);
  78. assert(default_image);
  79. uint32 transp = SDL_MapRGB(default_image->format, 0x00, 0xff, 0xff);
  80. SDL_FillRect(default_image, NULL, transp);
  81. SDL_SetColorKey(default_image, SDL_SRCCOLORKEY, transp);
  82. // Load theme from preferences
  83. load_theme(theme);
  84. atexit(shutdown_dialogs);
  85. }
  86. /*
  87. * Shutdown dialog manager
  88. */
  89. static void shutdown_dialogs(void)
  90. {
  91. unload_theme();
  92. }
  93. /*
  94. * Theme MML parser
  95. */
  96. class XML_ImageParser : public XML_ElementParser {
  97. public:
  98. XML_ImageParser(int base, int num = 1) : XML_ElementParser("image"), base_index(base), max_index(num - 1) {}
  99. bool Start()
  100. {
  101. have_index = have_name = false;
  102. scale = false;
  103. return true;
  104. }
  105. bool HandleAttribute(const char *tag, const char *value)
  106. {
  107. if (StringsEqual(tag, "index")) {
  108. if (ReadBoundedNumericalValue(value, "%d", index, 0, max_index))
  109. have_index = true;
  110. else
  111. return false;
  112. } else if (StringsEqual(tag, "file")) {
  113. name = value;
  114. have_name = true;
  115. } else if (StringsEqual(tag, "scale")) {
  116. return ReadBooleanValue(value, scale);
  117. } else {
  118. UnrecognizedTag();
  119. return false;
  120. }
  121. return true;
  122. }
  123. bool AttributesDone()
  124. {
  125. if (!have_index || !have_name) {
  126. AttribsMissing();
  127. return false;
  128. }
  129. dialog_image_spec[base_index + index].name = name;
  130. dialog_image_spec[base_index + index].scale = scale;
  131. return true;
  132. }
  133. private:
  134. int base_index, max_index;
  135. bool have_index, have_name;
  136. int index;
  137. string name;
  138. bool scale;
  139. };
  140. static XML_ImageParser FrameImageParser(FRAME_TL_IMAGE, 8);
  141. static XML_ImageParser ListImageParser(LIST_TL_IMAGE, 8);
  142. static XML_ImageParser ThumbImageParser(THUMB_T_IMAGE, 5);
  143. static XML_ImageParser SliderImageParser(SLIDER_L_IMAGE, 4);
  144. static XML_ImageParser ButtonImageParser(BUTTON_L_IMAGE, 3);
  145. class XML_DColorParser : public XML_ElementParser {
  146. public:
  147. XML_DColorParser(int base, int num = 1) : XML_ElementParser("color"), base_index(base), max_index(num - 1) {}
  148. bool Start()
  149. {
  150. have_red = have_green = have_blue = false;
  151. idx = 0;
  152. return true;
  153. }
  154. bool HandleAttribute(const char *tag, const char *value)
  155. {
  156. float v;
  157. if (StringsEqual(tag, "index")) {
  158. return ReadBoundedNumericalValue(value, "%d", idx, 0, max_index);
  159. } else if (StringsEqual(tag, "red")) {
  160. if (ReadNumericalValue(value, "%f", v)) {
  161. have_red = true;
  162. color.r = uint8(PIN(255 * v + 0.5, 0, 255));
  163. } else
  164. return false;
  165. } else if (StringsEqual(tag, "green")) {
  166. if (ReadNumericalValue(value, "%f", v)) {
  167. have_green = true;
  168. color.g = uint8(PIN(255 * v + 0.5, 0, 255));
  169. } else
  170. return false;
  171. } else if (StringsEqual(tag, "blue")) {
  172. if (ReadNumericalValue(value, "%f", v)) {
  173. have_blue = true;
  174. color.b = uint8(PIN(255 * v + 0.5, 0, 255));
  175. } else
  176. return false;
  177. } else {
  178. UnrecognizedTag();
  179. return false;
  180. }
  181. return true;
  182. }
  183. bool AttributesDone()
  184. {
  185. if (!have_red || !have_green || !have_blue) {
  186. AttribsMissing();
  187. return false;
  188. }
  189. dialog_color[base_index + idx] = color;
  190. return true;
  191. }
  192. private:
  193. int base_index, max_index;
  194. bool have_red, have_green, have_blue;
  195. int idx;
  196. SDL_Color color;
  197. };
  198. static XML_DColorParser BackgroundColorParser(BACKGROUND_COLOR);
  199. static XML_DColorParser TitleColorParser(TITLE_COLOR);
  200. static XML_DColorParser ButtonColorParser(BUTTON_COLOR, 2);
  201. static XML_DColorParser LabelColorParser(LABEL_COLOR, 2);
  202. static XML_DColorParser ItemColorParser(ITEM_COLOR, 2);
  203. static XML_DColorParser MessageColorParser(MESSAGE_COLOR);
  204. static XML_DColorParser TextEntryColorParser(TEXT_ENTRY_COLOR, 3);
  205. class XML_DFontParser : public XML_ElementParser {
  206. public:
  207. XML_DFontParser(int i) : XML_ElementParser("font"), idx(i) {}
  208. bool Start()
  209. {
  210. have_id = have_size = false;
  211. style = 0;
  212. return true;
  213. }
  214. bool HandleAttribute(const char *tag, const char *value)
  215. {
  216. if (StringsEqual(tag, "id")) {
  217. if (ReadNumericalValue(value, "%d", id))
  218. have_id = true;
  219. else
  220. return false;
  221. } else if (StringsEqual(tag, "size")) {
  222. if (ReadNumericalValue(value, "%d", size))
  223. have_size = true;
  224. else
  225. return false;
  226. } else if (StringsEqual(tag, "style")) {
  227. return ReadNumericalValue(value, "%d", style);
  228. } else {
  229. UnrecognizedTag();
  230. return false;
  231. }
  232. return true;
  233. }
  234. bool AttributesDone()
  235. {
  236. if (!have_id || !have_size) {
  237. AttribsMissing();
  238. return false;
  239. }
  240. dialog_font_spec[idx].font = id;
  241. dialog_font_spec[idx].style = style;
  242. dialog_font_spec[idx].size = size;
  243. return true;
  244. }
  245. private:
  246. bool have_id, have_size;
  247. int idx;
  248. int id, size, style;
  249. };
  250. static XML_DFontParser TitleFontParser(TITLE_FONT);
  251. static XML_DFontParser ButtonFontParser(BUTTON_FONT);
  252. static XML_DFontParser LabelFontParser(LABEL_FONT);
  253. static XML_DFontParser ItemFontParser(ITEM_FONT);
  254. static XML_DFontParser MessageFontParser(MESSAGE_FONT);
  255. static XML_DFontParser TextEntryFontParser(TEXT_ENTRY_FONT);
  256. static XML_DFontParser TextBoxFontParser(TEXT_BOX_FONT);
  257. class XML_DFrameParser : public XML_ElementParser {
  258. public:
  259. XML_DFrameParser() : XML_ElementParser("frame") {}
  260. bool HandleAttribute(const char *tag, const char *value)
  261. {
  262. if (StringsEqual(tag, "top")) {
  263. return ReadNumericalValue(value, "%hu", dialog_space[FRAME_T_SPACE]);
  264. } else if (StringsEqual(tag, "bottom")) {
  265. return ReadNumericalValue(value, "%hu", dialog_space[FRAME_B_SPACE]);
  266. } else if (StringsEqual(tag, "left")) {
  267. return ReadNumericalValue(value, "%hu", dialog_space[FRAME_L_SPACE]);
  268. } else if (StringsEqual(tag, "right")) {
  269. return ReadNumericalValue(value, "%hu", dialog_space[FRAME_R_SPACE]);
  270. } else {
  271. UnrecognizedTag();
  272. return false;
  273. }
  274. return true;
  275. }
  276. };
  277. static XML_DFrameParser FrameParser;
  278. struct XML_BackgroundParser : public XML_ElementParser {XML_BackgroundParser() : XML_ElementParser("background") {}};
  279. static XML_BackgroundParser BackgroundParser;
  280. struct XML_TitleParser : public XML_ElementParser {XML_TitleParser() : XML_ElementParser("title") {}};
  281. static XML_TitleParser TitleParser;
  282. class XML_SpacerParser : public XML_ElementParser {
  283. public:
  284. XML_SpacerParser() : XML_ElementParser("spacer") {}
  285. bool HandleAttribute(const char *tag, const char *value)
  286. {
  287. if (StringsEqual(tag, "height")) {
  288. return ReadNumericalValue(value, "%hu", dialog_space[SPACER_HEIGHT]);
  289. } else {
  290. UnrecognizedTag();
  291. return false;
  292. }
  293. return true;
  294. }
  295. };
  296. static XML_SpacerParser SpacerParser;
  297. class XML_ButtonParser : public XML_ElementParser {
  298. public:
  299. XML_ButtonParser() : XML_ElementParser("button") {}
  300. bool HandleAttribute(const char *tag, const char *value)
  301. {
  302. if (StringsEqual(tag, "top")) {
  303. return ReadNumericalValue(value, "%hu", dialog_space[BUTTON_T_SPACE]);
  304. } else if (StringsEqual(tag, "left")) {
  305. return ReadNumericalValue(value, "%hu", dialog_space[BUTTON_L_SPACE]);
  306. } else if (StringsEqual(tag, "right")) {
  307. return ReadNumericalValue(value, "%hu", dialog_space[BUTTON_R_SPACE]);
  308. } else if (StringsEqual(tag, "height")) {
  309. return ReadNumericalValue(value, "%hu", dialog_space[BUTTON_HEIGHT]);
  310. } else {
  311. UnrecognizedTag();
  312. return false;
  313. }
  314. return true;
  315. }
  316. };
  317. static XML_ButtonParser ButtonParser;
  318. struct XML_LabelParser : public XML_ElementParser {XML_LabelParser() : XML_ElementParser("label") {}};
  319. static XML_LabelParser LabelParser;
  320. class XML_DItemParser : public XML_ElementParser {
  321. public:
  322. XML_DItemParser() : XML_ElementParser("item") {}
  323. bool HandleAttribute(const char *tag, const char *value)
  324. {
  325. if (StringsEqual(tag, "space")) {
  326. return ReadNumericalValue(value, "%hu", dialog_space[LABEL_ITEM_SPACE]);
  327. } else {
  328. UnrecognizedTag();
  329. return false;
  330. }
  331. return true;
  332. }
  333. };
  334. static XML_DItemParser ItemParser;
  335. struct XML_MessageParser : public XML_ElementParser {XML_MessageParser() : XML_ElementParser("message") {}};
  336. static XML_MessageParser MessageParser;
  337. struct XML_TextEntryParser : public XML_ElementParser {XML_TextEntryParser() : XML_ElementParser("text_entry") {}};
  338. static XML_TextEntryParser TextEntryParser;
  339. struct XML_TextBoxParser : public XML_ElementParser { XML_TextBoxParser() : XML_ElementParser("text_box") {}};
  340. static XML_TextBoxParser TextBoxParser;
  341. class XML_TroughParser : public XML_ElementParser {
  342. public:
  343. XML_TroughParser() : XML_ElementParser("trough") {}
  344. bool HandleAttribute(const char *tag, const char *value)
  345. {
  346. if (StringsEqual(tag, "top")) {
  347. return ReadNumericalValue(value, "%hu", dialog_space[TROUGH_T_SPACE]);
  348. } else if (StringsEqual(tag, "bottom")) {
  349. return ReadNumericalValue(value, "%hu", dialog_space[TROUGH_B_SPACE]);
  350. } else if (StringsEqual(tag, "right")) {
  351. return ReadNumericalValue(value, "%hu", dialog_space[TROUGH_R_SPACE]);
  352. } else if (StringsEqual(tag, "width")) {
  353. return ReadNumericalValue(value, "%hu", dialog_space[TROUGH_WIDTH]);
  354. } else {
  355. UnrecognizedTag();
  356. return false;
  357. }
  358. return true;
  359. }
  360. };
  361. static XML_TroughParser TroughParser;
  362. struct XML_ThumbParser : public XML_ElementParser {XML_ThumbParser() : XML_ElementParser("thumb") {}};
  363. static XML_ThumbParser ThumbParser;
  364. class XML_ListParser : public XML_ElementParser {
  365. public:
  366. XML_ListParser() : XML_ElementParser("list") {}
  367. bool HandleAttribute(const char *tag, const char *value)
  368. {
  369. if (StringsEqual(tag, "top")) {
  370. return ReadNumericalValue(value, "%hu", dialog_space[LIST_T_SPACE]);
  371. } else if (StringsEqual(tag, "bottom")) {
  372. return ReadNumericalValue(value, "%hu", dialog_space[LIST_B_SPACE]);
  373. } else if (StringsEqual(tag, "left")) {
  374. return ReadNumericalValue(value, "%hu", dialog_space[LIST_L_SPACE]);
  375. } else if (StringsEqual(tag, "right")) {
  376. return ReadNumericalValue(value, "%hu", dialog_space[LIST_R_SPACE]);
  377. } else {
  378. UnrecognizedTag();
  379. return false;
  380. }
  381. return true;
  382. }
  383. };
  384. static XML_ListParser ListParser;
  385. class XML_SliderParser : public XML_ElementParser {
  386. public:
  387. XML_SliderParser() : XML_ElementParser("slider") {}
  388. bool HandleAttribute(const char *tag, const char *value)
  389. {
  390. if (StringsEqual(tag, "top")) {
  391. return ReadNumericalValue(value, "%hu", dialog_space[SLIDER_T_SPACE]);
  392. } else if (StringsEqual(tag, "left")) {
  393. return ReadNumericalValue(value, "%hu", dialog_space[SLIDER_L_SPACE]);
  394. } else if (StringsEqual(tag, "right")) {
  395. return ReadNumericalValue(value, "%hu", dialog_space[SLIDER_R_SPACE]);
  396. } else {
  397. UnrecognizedTag();
  398. return false;
  399. }
  400. return true;
  401. }
  402. };
  403. static XML_SliderParser SliderParser;
  404. class XML_ThemeParser : public XML_ElementParser {
  405. public:
  406. XML_ThemeParser() : XML_ElementParser("theme") {}
  407. };
  408. static XML_ThemeParser ThemeParser;
  409. XML_ElementParser *Theme_GetParser()
  410. {
  411. FrameParser.AddChild(&FrameImageParser);
  412. ThemeParser.AddChild(&FrameParser);
  413. BackgroundParser.AddChild(&BackgroundColorParser);
  414. ThemeParser.AddChild(&BackgroundParser);
  415. TitleParser.AddChild(&TitleFontParser);
  416. TitleParser.AddChild(&TitleColorParser);
  417. ThemeParser.AddChild(&TitleParser);
  418. ThemeParser.AddChild(&SpacerParser);
  419. ButtonParser.AddChild(&ButtonFontParser);
  420. ButtonParser.AddChild(&ButtonColorParser);
  421. ButtonParser.AddChild(&ButtonImageParser);
  422. ThemeParser.AddChild(&ButtonParser);
  423. LabelParser.AddChild(&LabelFontParser);
  424. LabelParser.AddChild(&LabelColorParser);
  425. ThemeParser.AddChild(&LabelParser);
  426. ItemParser.AddChild(&ItemFontParser);
  427. ItemParser.AddChild(&ItemColorParser);
  428. ThemeParser.AddChild(&ItemParser);
  429. MessageParser.AddChild(&MessageFontParser);
  430. MessageParser.AddChild(&MessageColorParser);
  431. ThemeParser.AddChild(&MessageParser);
  432. TextEntryParser.AddChild(&TextEntryFontParser);
  433. TextEntryParser.AddChild(&TextEntryColorParser);
  434. ThemeParser.AddChild(&TextEntryParser);
  435. TextBoxParser.AddChild(&TextBoxFontParser);
  436. ThemeParser.AddChild(&TextBoxParser);
  437. ListParser.AddChild(&ListImageParser);
  438. ListParser.AddChild(&TroughParser);
  439. ThumbParser.AddChild(&ThumbImageParser);
  440. ListParser.AddChild(&ThumbParser);
  441. ThemeParser.AddChild(&ListParser);
  442. SliderParser.AddChild(&SliderImageParser);
  443. ThemeParser.AddChild(&SliderParser);
  444. return &ThemeParser;
  445. }
  446. /*
  447. * Load theme
  448. */
  449. void load_theme(FileSpecifier &theme)
  450. {
  451. // Unload previous theme
  452. unload_theme();
  453. // Set defaults, the theme overrides these
  454. set_theme_defaults();
  455. // Parse theme MML script
  456. FileSpecifier theme_mml = theme + "theme.mml";
  457. XML_Loader_SDL loader;
  458. loader.CurrentElement = &RootParser;
  459. loader.ParseFile(theme_mml);
  460. // Open resource file
  461. FileSpecifier theme_rsrc = theme + "resources";
  462. theme_rsrc.Open(theme_resources);
  463. clear_game_error();
  464. // Load fonts
  465. for (int i=0; i<NUM_DIALOG_FONTS; i++)
  466. dialog_font[i] = load_font(dialog_font_spec[i]);
  467. // Load images
  468. for (int i=0; i<NUM_DIALOG_IMAGES; i++) {
  469. FileSpecifier file = theme + dialog_image_spec[i].name;
  470. SDL_Surface *s =SDL_LoadBMP(file.GetPath());
  471. if (s)
  472. SDL_SetColorKey(s, SDL_SRCCOLORKEY, SDL_MapRGB(s->format, 0x00, 0xff, 0xff));
  473. dialog_image[i] = s;
  474. }
  475. }
  476. /*
  477. * Set theme default values
  478. */
  479. static const SDL_Color default_dialog_color[NUM_DIALOG_COLORS] = {
  480. {0x00, 0x00, 0x00}, // BACKGROUND COLOR
  481. {0xc0, 0xc0, 0xc0}, // TITLE_COLOR
  482. {0xc0, 0x00, 0x00}, // BUTTON_COLOR
  483. {0xff, 0xff, 0xff}, // BUTTON_ACTIVE_COLOR
  484. {0x20, 0x20, 0xff}, // LABEL_COLOR
  485. {0x80, 0x80, 0xff}, // LABEL_ACTIVE_COLOR
  486. {0x40, 0xff, 0x40}, // ITEM_COLOR
  487. {0xff, 0xff, 0xff}, // ITEM_ACTIVE_COLOR
  488. {0xff, 0xff, 0xff}, // MESSAGE_COLOR
  489. {0x40, 0xff, 0x40}, // TEXT_ENTRY_COLOR
  490. {0xff, 0xff, 0xff}, // TEXT_ENTRY_ACTIVE_COLOR
  491. {0xff, 0xff, 0xff}, // TEXT_ENTRY_CURSOR_COLOR
  492. {0x60, 0x60, 0x60} // KEY_BINDING_COLOR
  493. };
  494. static const int default_dialog_space[NUM_DIALOG_SPACES] = {
  495. 6, // FRAME_T_SPACE
  496. 6, // FRAME_L_SPACE
  497. 6, // FRAME_R_SPACE
  498. 6, // FRAME_B_SPACE
  499. 8, // SPACER_HEIGHT
  500. 16, // LABEL_ITEM_SPACE
  501. 2, // LIST_T_SPACE
  502. 2, // LIST_L_SPACE
  503. 18, // LIST_R_SPACE
  504. 2, // LIST_B_SPACE
  505. 0, // TROUGH_T_SPACE
  506. 16, // TROUGH_R_SPACE
  507. 0, // TROUGH_B_SPACE
  508. 16, // TROUGH_WIDTH
  509. 0, // SLIDER_T_SPACE
  510. 0, // SLIDER_L_SPACE
  511. 0, // SLIDER_R_SPACE
  512. 0, // BUTTON_T_SPACE
  513. 0, // BUTTON_L_SPACE
  514. 0, // BUTTON_R_SPACE
  515. 14 // BUTTON_HEIGHT
  516. };
  517. static void set_theme_defaults(void)
  518. {
  519. for (int i=0; i<NUM_DIALOG_FONTS; i++)
  520. dialog_font[i] = NULL;
  521. for (int i=0; i<NUM_DIALOG_COLORS; i++)
  522. dialog_color[i] = default_dialog_color[i];
  523. for (int i=0; i<NUM_DIALOG_IMAGES; i++) {
  524. dialog_image_spec[i].name = "";
  525. dialog_image_spec[i].scale = false;
  526. dialog_image[i] = NULL;
  527. }
  528. for (int i=0; i<NUM_DIALOG_SPACES; i++)
  529. dialog_space[i] = default_dialog_space[i];
  530. }
  531. /*
  532. * Unload theme
  533. */
  534. static void unload_theme(void)
  535. {
  536. // Unload fonts
  537. for (int i=0; i<NUM_DIALOG_FONTS; i++)
  538. if (dialog_font[i]) {
  539. unload_font(dialog_font[i]);
  540. dialog_font[i] = NULL;
  541. }
  542. // Free surfaces
  543. for (int i=0; i<NUM_DIALOG_IMAGES; i++)
  544. if (dialog_image[i]) {
  545. SDL_FreeSurface(dialog_image[i]);
  546. dialog_image[i] = NULL;
  547. }
  548. // Close resource file
  549. theme_resources.Close();
  550. }
  551. /*
  552. * Get dialog font/color/image/space from theme
  553. */
  554. const sdl_font_info *get_dialog_font(int which, uint16 &style)
  555. {
  556. assert(which >= 0 && which < NUM_DIALOG_FONTS);
  557. const sdl_font_info *font = dialog_font[which];
  558. if (font) {
  559. style = dialog_font_spec[which].style;
  560. return font;
  561. } else {
  562. style = styleNormal;
  563. return default_font;
  564. }
  565. }
  566. uint32 get_dialog_color(int which)
  567. {
  568. assert(which >= 0 && which < NUM_DIALOG_COLORS);
  569. return SDL_MapRGB(dialog_surface->format, dialog_color[which].r, dialog_color[which].g, dialog_color[which].b);
  570. }
  571. // ZZZ: added this for convenience; taken from w_player_color::draw().
  572. // Obviously, this color does not come from the theme.
  573. uint32 get_dialog_player_color(size_t colorIndex) {
  574. SDL_Color c;
  575. _get_interface_color(PLAYER_COLOR_BASE_INDEX + colorIndex, &c);
  576. return SDL_MapRGB(dialog_surface->format, c.r, c.g, c.b);
  577. }
  578. SDL_Surface *get_dialog_image(int which, int width, int height)
  579. {
  580. assert(which >= 0 && which < NUM_DIALOG_IMAGES);
  581. SDL_Surface *s = dialog_image[which];
  582. if (s == NULL)
  583. s = default_image;
  584. // If no width and height is given, the surface is returned as-is and must
  585. // not be freed by the caller
  586. if (width == 0 && height == 0)
  587. return s;
  588. // Otherwise, a new tiled/rescaled surface is created which must be freed
  589. // by the caller
  590. int req_width = width ? width : s->w;
  591. int req_height = height ? height : s->h;
  592. SDL_Surface *s2 = dialog_image_spec[which].scale ? rescale_surface(s, req_width, req_height) : tile_surface(s, req_width, req_height);
  593. SDL_SetColorKey(s2, SDL_SRCCOLORKEY, SDL_MapRGB(s2->format, 0x00, 0xff, 0xff));
  594. return s2;
  595. }
  596. uint16 get_dialog_space(size_t which)
  597. {
  598. assert(which < NUM_DIALOG_SPACES);
  599. return dialog_space[which];
  600. }
  601. /*
  602. * Play dialog sound
  603. */
  604. void play_dialog_sound(int which)
  605. {
  606. play_sound(which, NULL, NONE);
  607. }
  608. /*
  609. * Dialog constructor
  610. */
  611. dialog::dialog() : active_widget(NULL), active_widget_num(UNONE), done(false),
  612. cursor_was_visible(false), parent_dialog(NULL),
  613. processing_function(NULL), active_tab(0)
  614. {
  615. }
  616. /*
  617. * Dialog destructor
  618. */
  619. dialog::~dialog()
  620. {
  621. // Free all widgets
  622. vector<widget *>::const_iterator i = widgets.begin(), end = widgets.end();
  623. while (i != end) {
  624. delete *i;
  625. i++;
  626. }
  627. }
  628. /*
  629. * Add widget
  630. */
  631. void dialog::add(widget *w)
  632. {
  633. widgets.push_back(w);
  634. tabs.push_back(NONE);
  635. w->set_owning_dialog(this);
  636. }
  637. void dialog::add_to_tab(widget *w, int tab)
  638. {
  639. widgets.push_back(w);
  640. tabs.push_back(tab);
  641. w->set_owning_dialog(this);
  642. }
  643. /*
  644. * Layout dialog
  645. */
  646. void dialog::layout()
  647. {
  648. // Layout all widgets, calculate total width and height
  649. int y = get_dialog_space(FRAME_T_SPACE);
  650. int left = 0;
  651. int right = 0;
  652. // Initialise tab-specific y's
  653. map<int, int> y_per_tab;
  654. for (vector<int>::const_iterator tab = tabs.begin(); tab != tabs.end(); ++tab)
  655. if (*tab != NONE)
  656. y_per_tab[*tab] = y;
  657. vector<widget *>::const_iterator i = widgets.begin(), end = widgets.end();
  658. vector<int>::const_iterator j = tabs.begin();
  659. while (i != end) {
  660. // Are we interested in the global y or a tab-specific?
  661. int& the_y = (*j == NONE) ? y : y_per_tab[*j];
  662. widget *w = *i;
  663. w->rect.y = the_y;
  664. the_y += w->layout();
  665. if (w->rect.x < left)
  666. left = w->rect.x;
  667. if (w->rect.x + w->rect.w > right)
  668. right = w->rect.x + w->rect.w;
  669. if (*j == NONE)
  670. // Later tabbed widgets must appear below this widget
  671. for (map<int, int>::iterator tab = y_per_tab.begin(); tab != y_per_tab.end(); ++tab)
  672. (*tab).second = y;
  673. else
  674. // The next untabbed widget must appear below this widget
  675. if (y < y_per_tab[*j])
  676. y = y_per_tab[*j];
  677. i++;
  678. j++;
  679. }
  680. left = abs(left);
  681. rect.w = (left > right ? left : right) * 2 + get_dialog_space(FRAME_L_SPACE) + get_dialog_space(FRAME_R_SPACE);
  682. rect.h = y + get_dialog_space(FRAME_B_SPACE);
  683. // Center dialog on video surface
  684. SDL_Surface *video = SDL_GetVideoSurface();
  685. rect.x = (video->w - rect.w) / 2;
  686. rect.y = (video->h - rect.h) / 2;
  687. // Transform positions of all widgets to be relative to the dialog surface
  688. i = widgets.begin();
  689. int offset_x = (rect.w - get_dialog_space(FRAME_L_SPACE) - get_dialog_space(FRAME_R_SPACE)) / 2 + get_dialog_space(FRAME_L_SPACE);
  690. // ZZZ: to provide more detailed layout info to widgets
  691. int leftmost_x = get_dialog_space(FRAME_L_SPACE);
  692. int usable_width = rect.w - leftmost_x - get_dialog_space(FRAME_R_SPACE);
  693. while (i != end) {
  694. (*i)->rect.x += offset_x;
  695. (*i)->capture_layout_information(leftmost_x, usable_width);
  696. i++;
  697. }
  698. }
  699. /*
  700. * Update part of dialog on screen
  701. */
  702. void dialog::update(SDL_Rect r) const
  703. {
  704. SDL_Surface *video = SDL_GetVideoSurface();
  705. // ZZZ: this less efficient way (copies more stuff) is an attempt to fix dialog updating
  706. // in the face of double-buffered page-flipping.
  707. /*
  708. SDL_Rect dst_rect = {r.x + rect.x, r.y + rect.y, r.w, r.h};
  709. SDL_BlitSurface(dialog_surface, &r, video, &dst_rect);
  710. SDL_UpdateRects(video, 1, &dst_rect);
  711. */
  712. // this is needed because 'rect' is const here and SDL_* don't seem to use const.
  713. SDL_Rect dst_rect = rect;
  714. SDL_Rect src_rect = { 0, 0, rect.w, rect.h };
  715. SDL_BlitSurface(dialog_surface, &src_rect, video, &dst_rect);
  716. SDL_UpdateRects(video, 1, &dst_rect);
  717. #ifdef HAVE_OPENGL
  718. if (video->flags & SDL_OPENGL)
  719. SDL_GL_SwapBuffers();
  720. #endif
  721. }
  722. /*
  723. * Draw dialog
  724. */
  725. void dialog::draw_widget(widget *w, bool do_update) const
  726. {
  727. // Clear and redraw widget
  728. SDL_FillRect(dialog_surface, &w->rect, get_dialog_color(BACKGROUND_COLOR));
  729. w->draw(dialog_surface);
  730. w->dirty = false;
  731. // Blit to screen
  732. if (do_update)
  733. update(w->rect);
  734. }
  735. static void draw_frame_image(SDL_Surface *s, int x, int y)
  736. {
  737. SDL_Rect r = {x, y, s->w, s->h};
  738. SDL_BlitSurface(s, NULL, dialog_surface, &r);
  739. }
  740. void dialog::draw(void) const
  741. {
  742. // Clear dialog surface
  743. SDL_FillRect(dialog_surface, NULL, get_dialog_color(BACKGROUND_COLOR));
  744. // Draw frame
  745. draw_frame_image(frame_tl, 0, 0);
  746. draw_frame_image(frame_t, frame_tl->w, 0);
  747. draw_frame_image(frame_tr, frame_tl->w + frame_t->w, 0);
  748. draw_frame_image(frame_l, 0, frame_tl->h);
  749. draw_frame_image(frame_r, rect.w - frame_r->w, frame_tr->h);
  750. draw_frame_image(frame_bl, 0, frame_tl->h + frame_l->h);
  751. draw_frame_image(frame_b, frame_bl->w, rect.h - frame_b->h);
  752. draw_frame_image(frame_br, frame_bl->w + frame_b->w, frame_tr->h + frame_r->h);
  753. // Draw all widgets, except those with inactive tabs
  754. vector<widget *>::const_iterator i = widgets.begin(), end = widgets.end();
  755. vector<int>::const_iterator j = tabs.begin();
  756. while (i != end) {
  757. if (*j == NONE || *j == active_tab)
  758. draw_widget(*i, false);
  759. i++;
  760. j++;
  761. }
  762. // Blit to screen
  763. SDL_Rect r = {0, 0, rect.w, rect.h};
  764. update(r);
  765. }
  766. void
  767. dialog::draw_dirty_widgets() const
  768. {
  769. for (unsigned i=0; i<widgets.size(); i++)
  770. if (widgets[i]->dirty)
  771. if (tabs[i] == NONE || tabs[i] == active_tab)
  772. draw_widget(widgets[i]);
  773. }
  774. /*
  775. * Deactivate currently active widget
  776. */
  777. void dialog::deactivate_currently_active_widget(bool draw)
  778. {
  779. if (active_widget) {
  780. active_widget->active = false;
  781. if (draw)
  782. draw_widget(active_widget);
  783. active_widget = NULL;
  784. active_widget_num = UNONE;
  785. }
  786. }
  787. /*
  788. * Activate widget
  789. */
  790. void dialog::activate_widget(size_t num, bool draw)
  791. {
  792. if (num == active_widget_num)
  793. return;
  794. // BUG: may crash if num==UNONE or NONE
  795. if (!widgets[num]->is_selectable())
  796. return;
  797. // Deactivate previously active widget
  798. deactivate_currently_active_widget(draw);
  799. // Activate new widget
  800. active_widget = widgets[num];
  801. active_widget_num = num;
  802. active_widget->active = true;
  803. if (draw) {
  804. draw_widget(active_widget);
  805. // play_dialog_sound(DIALOG_SELECT_SOUND);
  806. }
  807. }
  808. /*
  809. * Activate first selectable widget (don't draw)
  810. */
  811. void dialog::activate_first_widget(void)
  812. {
  813. for (size_t i=0; i<widgets.size(); i++) {
  814. if (widgets[i]->is_selectable() && (tabs[i] == NONE || tabs[i] == active_tab)) {
  815. activate_widget(i, false);
  816. break;
  817. }
  818. }
  819. }
  820. /*
  821. * Activate next/previous selectable widget
  822. */
  823. void dialog::activate_next_widget(void)
  824. {
  825. size_t i = active_widget_num;
  826. // BUG: infinate loop if active_widget_num == UNONE or NONE
  827. do {
  828. i++;
  829. if (i >= int(widgets.size()))
  830. i = 0;
  831. } while (!(widgets[i]->is_selectable() && (tabs[i] == NONE || tabs[i] == active_tab)) && i != active_widget_num);
  832. // Either widgets[i] is selectable, or i == active_widget_num (in which case we wrapped all the way around)
  833. if (widgets[i]->is_selectable())
  834. activate_widget(i);
  835. else
  836. deactivate_currently_active_widget();
  837. }
  838. void dialog::activate_prev_widget(void)
  839. {
  840. size_t i = active_widget_num;
  841. // BUG: infinate loop if active_widget_num == UNONE or NONE
  842. do {
  843. if (i == 0)
  844. i = widgets.size() - 1;
  845. else
  846. i--;
  847. } while (!(widgets[i]->is_selectable() && (tabs[i] == NONE || tabs[i] == active_tab)) && i != active_widget_num);
  848. // Either widgets[i] is selectable, or i == active_widget_num (in which case we wrapped all the way around)
  849. if (widgets[i]->is_selectable())
  850. activate_widget(i);
  851. else
  852. deactivate_currently_active_widget();
  853. }
  854. /*
  855. * Find widget given video surface coordinates (<0 = none found)
  856. */
  857. int dialog::find_widget(int x, int y)
  858. {
  859. // Transform to dialog coordinates
  860. x -= rect.x;
  861. y -= rect.y;
  862. // Find widget
  863. vector<int>::const_iterator j = tabs.begin();
  864. vector<widget *>::const_iterator i = widgets.begin(), end = widgets.end();
  865. int num = 0;
  866. while (i != end) {
  867. widget *w = *i;
  868. if (*j == NONE || *j == active_tab) // Don't find widgets in inactive tabs
  869. if (x >= w->rect.x && y >= w->rect.y && x < w->rect.x + w->rect.w && y < w->rect.y + w->rect.h)
  870. return num;
  871. i++; j++; num++;
  872. }
  873. return -1;
  874. }
  875. /*
  876. * Find widget by its numeric ID
  877. */
  878. widget *dialog::get_widget_by_id(short inID) const
  879. {
  880. // Find first matching widget
  881. vector<widget *>::const_iterator i = widgets.begin(), end = widgets.end();
  882. while (i != end) {
  883. widget *w = *i;
  884. if (w->get_identifier() == inID)
  885. return w;
  886. i++;
  887. }
  888. return NULL;
  889. }
  890. /*
  891. * Handle event
  892. */
  893. void dialog::event(SDL_Event &e)
  894. {
  895. bool handled = false;
  896. // handle events we do not want widgets to see or modify
  897. switch (e.type) {
  898. case SDL_KEYDOWN:
  899. if (e.key.keysym.sym == SDLK_RETURN
  900. && ((e.key.keysym.mod & KMOD_ALT) || (e.key.keysym.mod & KMOD_META))) {
  901. toggle_fullscreen();
  902. draw();
  903. handled = true;
  904. }
  905. break;
  906. case SDL_ACTIVEEVENT:
  907. if (e.active.state & SDL_APPACTIVE) {
  908. if (e.active.gain) {
  909. draw();
  910. handled = true;
  911. }
  912. }
  913. break;
  914. }
  915. if (!handled) {
  916. // First pass event to active widget (which may modify it)
  917. if (active_widget)
  918. active_widget->event(e);
  919. // Remaining events handled by dialog
  920. switch (e.type) {
  921. // Key pressed
  922. case SDL_KEYDOWN:
  923. switch (e.key.keysym.sym) {
  924. case SDLK_ESCAPE: // ESC = Exit dialog
  925. quit(-1);
  926. break;
  927. case SDLK_UP: // Up = Activate previous widget
  928. case SDLK_LEFT:
  929. activate_prev_widget();
  930. break;
  931. case SDLK_DOWN: // Down = Activate next widget
  932. case SDLK_RIGHT:
  933. activate_next_widget();
  934. break;
  935. case SDLK_TAB:
  936. if (e.key.keysym.mod & KMOD_SHIFT)
  937. activate_prev_widget();
  938. else
  939. activate_next_widget();
  940. break;
  941. case SDLK_RETURN: // Return = Action on widget
  942. active_widget->click(0, 0);
  943. break;
  944. case SDLK_F9: // F9 = Screen dump
  945. dump_screen();
  946. break;
  947. default:
  948. break;
  949. }
  950. break;
  951. // Mouse moved
  952. case SDL_MOUSEMOTION: {
  953. int x = e.motion.x, y = e.motion.y;
  954. int num = find_widget(x, y);
  955. if (num >= 0) {
  956. assert(num == (size_t)num);
  957. activate_widget(num);
  958. widget *w = widgets[num];
  959. w->mouse_move(x - rect.x - w->rect.x, y - rect.y - w->rect.y);
  960. }
  961. break;
  962. }
  963. // Mouse button pressed
  964. case SDL_MOUSEBUTTONDOWN: {
  965. int x = e.button.x, y = e.button.y;
  966. int num = find_widget(x, y);
  967. if (num >= 0) {
  968. widget *w = widgets[num];
  969. w->click(x - rect.x - w->rect.x, y - rect.y - w->rect.y);
  970. }
  971. break;
  972. }
  973. // Quit requested
  974. case SDL_QUIT:
  975. exit(0);
  976. break;
  977. }
  978. }
  979. // Redraw dirty widgets
  980. draw_dirty_widgets();
  981. }
  982. /*
  983. * Run dialog modally, returns result code (0 = ok, -1 = cancel)
  984. */
  985. int dialog::run(bool intro_exit_sounds)
  986. {
  987. // Put dialog on screen
  988. start(intro_exit_sounds);
  989. // Run dialog loop
  990. while (!done) {
  991. // Process events
  992. process_events();
  993. if (done)
  994. break;
  995. // Run custom processing function
  996. if (processing_function)
  997. processing_function(this);
  998. // Give time to system
  999. global_idle_proc();
  1000. SDL_Delay(10);
  1001. }
  1002. // Remove dialog from screen
  1003. return finish(intro_exit_sounds);
  1004. }
  1005. /*
  1006. * Put dialog on screen
  1007. */
  1008. void dialog::start(bool play_sound)
  1009. {
  1010. // Make sure nobody tries re-entrancy with us
  1011. assert(!done);
  1012. // Set new active dialog
  1013. parent_dialog = top_dialog;
  1014. top_dialog = this;
  1015. // Clear dialog surface
  1016. SDL_FillRect(dialog_surface, NULL, get_dialog_color(BACKGROUND_COLOR));
  1017. // Activate first widget
  1018. activate_first_widget();
  1019. // Layout dialog
  1020. layout();
  1021. // Get frame images
  1022. frame_tl = get_dialog_image(FRAME_TL_IMAGE);
  1023. frame_tr = get_dialog_image(FRAME_TR_IMAGE);
  1024. frame_bl = get_dialog_image(FRAME_BL_IMAGE);
  1025. frame_br = get_dialog_image(FRAME_BR_IMAGE);
  1026. frame_t = get_dialog_image(FRAME_T_IMAGE, rect.w - frame_tl->w - frame_tr->w, 0);
  1027. frame_l = get_dialog_image(FRAME_L_IMAGE, 0, rect.h - frame_tl->h - frame_bl->h);
  1028. frame_r = get_dialog_image(FRAME_R_IMAGE, 0, rect.h - frame_tr->h - frame_br->h);
  1029. frame_b = get_dialog_image(FRAME_B_IMAGE, rect.w - frame_bl->w - frame_br->w, 0);
  1030. // Draw dialog
  1031. #if defined(MAC_SDL_KLUDGE)
  1032. clear_screen();
  1033. #endif
  1034. draw();
  1035. // Show cursor
  1036. cursor_was_visible = (SDL_ShowCursor(SDL_ENABLE) == SDL_ENABLE);
  1037. // Welcome sound
  1038. if (play_sound)
  1039. play_dialog_sound(DIALOG_INTRO_SOUND);
  1040. // Enable unicode key translation
  1041. // (ZZZ: hmm maybe this should be done on entering/leaving run_a_little?
  1042. // I guess really we need to "push" the current UNICODE state and set it true on entering run_a_little,
  1043. // then "pop" the previous state on leaving run_a_little. In the meantime, we hope nobody tries their own
  1044. // event processing between dialog::start and dialog::finish (or at least is prepared to live with UNICODE) :) )
  1045. saved_unicode_state = SDL_EnableUNICODE(true);
  1046. // Prepare for dialog event loop
  1047. result = 0;
  1048. done = false;
  1049. }
  1050. /*
  1051. * Process pending dialog events
  1052. */
  1053. bool dialog::process_events()
  1054. {
  1055. while (!done) {
  1056. // Get next event
  1057. SDL_Event e;
  1058. e.type = SDL_NOEVENT;
  1059. SDL_PollEvent(&e);
  1060. if (e.type == SDL_NOEVENT)
  1061. break;
  1062. // Handle event
  1063. event(e);
  1064. }
  1065. return done;
  1066. }
  1067. /*
  1068. * Remove dialog from screen
  1069. */
  1070. int dialog::finish(bool play_sound)
  1071. {
  1072. // Disable unicode key translation
  1073. SDL_EnableUNICODE(saved_unicode_state);
  1074. // Farewell sound
  1075. if (play_sound)
  1076. play_dialog_sound(result == 0 ? DIALOG_OK_SOUND : DIALOG_CANCEL_SOUND);
  1077. // Hide cursor
  1078. if (!cursor_was_visible)
  1079. SDL_ShowCursor(false);
  1080. // Clear dialog surface
  1081. SDL_FillRect(dialog_surface, NULL, get_dialog_color(BACKGROUND_COLOR));
  1082. // Erase dialog from screen
  1083. SDL_Surface *video = SDL_GetVideoSurface();
  1084. SDL_FillRect(video, &rect, SDL_MapRGB(video->format, 0, 0, 0));
  1085. SDL_UpdateRects(video, 1, &rect);
  1086. #ifdef HAVE_OPENGL
  1087. if (video->flags & SDL_OPENGL)
  1088. SDL_GL_SwapBuffers();
  1089. #endif
  1090. // Free frame images
  1091. if (frame_t) SDL_FreeSurface(frame_t);
  1092. if (frame_l) SDL_FreeSurface(frame_l);
  1093. if (frame_r) SDL_FreeSurface(frame_r);
  1094. if (frame_b) SDL_FreeSurface(frame_b);
  1095. // Restore active dialog
  1096. top_dialog = parent_dialog;
  1097. parent_dialog = NULL;
  1098. if (top_dialog) {
  1099. clear_screen();
  1100. top_dialog->draw();
  1101. }
  1102. // Allow dialog to be run again later
  1103. done = false;
  1104. return result;
  1105. }
  1106. /*
  1107. * Quit dialog, return result
  1108. */
  1109. void dialog::quit(int r)
  1110. {
  1111. result = r;
  1112. done = true;
  1113. }
  1114. /*
  1115. * Standard callback functions
  1116. */
  1117. void dialog_ok(void *arg)
  1118. {
  1119. dialog *d = (dialog *)arg;
  1120. d->quit(0);
  1121. }
  1122. void dialog_cancel(void *arg)
  1123. {
  1124. dialog *d = (dialog *)arg;
  1125. d->quit(-1);
  1126. }
  1127. // ZZZ: commonly-used callback for text entry enter_pressed_callback
  1128. void dialog_try_ok(w_text_entry* text_entry) {
  1129. w_button* ok_button = dynamic_cast<w_button*>(text_entry->get_owning_dialog()->get_widget_by_id(iOK));
  1130. // This is arguably a violation of the sdl_dialog/sdl_widget standard behavior since
  1131. // ok_button is clicked without first becoming the active widget. With the current
  1132. // implementation of w_button::click, should not be a problem...
  1133. if(ok_button != NULL)
  1134. ok_button->click(0,0);
  1135. }
  1136. // ZZZ: commonly-used callback to enable/disable "OK" based on whether a text_entry has data
  1137. void dialog_disable_ok_if_empty(w_text_entry* inTextEntry) {
  1138. modify_control_enabled(inTextEntry->get_owning_dialog(), iOK,
  1139. (inTextEntry->get_text()[0] == 0) ? CONTROL_INACTIVE : CONTROL_ACTIVE);
  1140. }