20130313
[gdash.git] / src / framework / app.cpp
blobafaabf6c61821bda2340effe3f620f662f2e2570
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
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.
17 /**
18 * @defgroup Framework
20 * This group is the documentation of the Activity the App and
21 * the Command class, which together control the graphical presentation
22 * of everything in GDash (except the editor) to the user.
24 * An Activity object is a screen on which the user can do something,
25 * for example play the game, select a file or read about a cave. Most
26 * of the activities fill the whole screen. (Some of them are small
27 * windows centered on the screen, but still they are modal.) An
28 * activity receives events from the application, and can organize its
29 * inner workings according to these events. There are keypress, timer,
30 * redraw and other kinds of events.
32 * The activities are contained in an App object which organizes them into
33 * a stack. Every time an event is received from the user or the timer,
34 * the App object forwards the events (maybe after some preprocessing)
35 * to the topmost activity on the stack. New activities are put on top
36 * of the stack, and an activity can quit by requesting the App to
37 * pop it off the top of the stack.
39 * The App object has the responsibility to connect the low level user
40 * interface (screen, keyboard, graphics objects) to the activities.
41 * The main program (which may be an SDL or a GTK implementation)
42 * creates the App object, and translates the operating system and
43 * graphics library specific events to function calls on the App
44 * object. For example, when the user presses the Escape key, the
45 * .keyboard_event() method of the App is to be called; and after that,
46 * the App will forward this event to the topmost activity. If it is
47 * the title screen, the program will presumably quit. If it is the
48 * game, the cave will be restarted. It is always only the topmost
49 * activity, which will receive the events from the app. Those which
50 * are occluded by these do not receive any events, and so they are
51 * stopped.
53 * In order to provide a means of communication between activities,
54 * Command objects can be created. Each activity is allowed to push
55 * as many command objects as it wants into the command queue of the
56 * App from any of its event methods. The App will check the command
57 * queue after calling any of the event methods of the topmost activity,
58 * and execute all commands which are queued.
60 * The starting of the game from the title screen is a typical use of
61 * these commands objects. When the user presses space, his name is asked
62 * for. After that, he can start the game by typing his name and pressing
63 * enter. This can be implemented by the TitleScreenActivity, the
64 * InputTextActivity and the GameActivity classes.
65 * - First, the TitleScreenActivity creates the InputTextActivity.
66 * - The InputTextActivity does not know, what it should do with the
67 * text the user typed.
68 * - However, it is also given a NewGameCommand, which is a command
69 * of one string parameter. When this command is executed, it creates
70 * a GameActivity and gives it to the App to push it onto the stack.
71 * - If the user presses enter after typing his name, the
72 * InputTextActivity is popped from the stack, and the GameActivity
73 * is pushed.
74 * - If the user presses escape, the command is not executed, so after
75 * popping the InputTextActivity nothing else happens. And so the
76 * TitleScreenActivity is on the top again.
78 * The command queue also serves the purpose of preventing object
79 * creation/destruction ordering problems from happening. For example,
80 * in the above scenario, the InputTextActivity object should not be
81 * destructed before setting the player name parameter in the NewGameCommand
82 * and executing the command itself. So activities are also pushed and
83 * popped by means of using PopActivityCommand and PushActivityCommand
84 * commands. The above interaction in detail:
85 * - The user presses enter after typing his name.
86 * - The NewGameCommand is parametrized with the name of the user.
87 * - A PopActivityCommand is enqueued in the App.
88 * - The NewGameCommand is also enqueued.
89 * - The App checks the command queue.
90 * - First, it executes the PopActivityCommand, so the InputTextActivity
91 * is destructed. (The name and starting cave parameters are not lost,
92 * because they are in the NewGameCommand.)
93 * - The NewGameCommand is executed. It creates the GameActivity and
94 * pushes it onto the top of the activity stack.
95 * - The game runs.
97 * The App class has some methods which can be called for common tasks like
98 * selecting a file or asking a user a yes or no question. By default, these
99 * methods push the corresponding activity. However, they can be overridden
100 * by inheriting from the App, to provide the user with toolkit-specific
101 * dialog boxes, for example GTK+ file open dialogs or message dialogs.
102 * Activities should use these methods to perform these tasks instead of
103 * hard-coding anything.
106 #include "config.h"
108 #include <algorithm>
109 #include <glib/gi18n.h>
111 #include "framework/app.hpp"
112 #include "framework/commands.hpp"
113 #include "cave/helper/colors.hpp"
114 #include "gfx/pixbuf.hpp"
115 #include "gfx/fontmanager.hpp"
116 #include "gfx/pixbuffactory.hpp"
117 #include "gfx/screen.hpp"
118 #include "framework/commands.hpp"
119 #include "input/gameinputhandler.hpp"
121 #include "misc/about.hpp"
122 #include "sound/sound.hpp"
123 #include "settings.hpp"
125 #include "framework/selectfileactivity.hpp"
126 #include "framework/askyesnoactivity.hpp"
127 #include "framework/showtextactivity.hpp"
128 #include "framework/inputtextactivity.hpp"
129 #include "framework/volumeactivity.hpp"
130 #include "framework/settingsactivity.hpp"
131 #include "framework/messageactivity.hpp"
133 #include "background.cpp"
135 std::string help_strings_to_string(char const **strings) {
136 std::string s;
137 for (int n=0; strings[n] != NULL; n+=2)
138 s += SPrintf("%c%s %c%s\n") % GD_COLOR_INDEX_YELLOW % strings[n] % GD_COLOR_INDEX_LIGHTBLUE % strings[n+1];
139 return s;
143 App::App() {
144 pixbuf_factory = NULL;
145 font_manager = NULL;
146 screen = NULL;
147 gameinput = NULL;
148 caveset = NULL;
149 background_image = NULL;
153 App::~App() {
154 pop_all_activities();
155 delete background_image;
156 delete screen;
157 delete gameinput;
158 delete font_manager;
159 delete pixbuf_factory;
160 gd_music_stop();
164 void App::set_no_activity_command(SmartPtr<Command> command) {
165 no_activity_command = command;
169 void App::set_quit_event_command(SmartPtr<Command> command) {
170 quit_event_command = command;
174 void App::set_request_restart_command(SmartPtr<Command> command) {
175 request_restart_command = command;
179 void App::set_start_editor_command(SmartPtr<Command> command) {
180 start_editor_command = command;
184 void App::clear_screen() {
185 if (!background_image) {
186 Pixbuf *pixbuf = pixbuf_factory->create_from_inline(sizeof(background), background);
187 background_image = pixbuf_factory->create_pixmap_from_pixbuf(*pixbuf, false);
188 delete pixbuf;
190 screen->blit(*background_image, 0, 0);
194 void App::draw_window(int rx, int ry, int rw, int rh) {
195 int const size = pixbuf_factory->get_pixmap_scale();
196 screen->fill_rect(rx-2*size, ry-2*size, rw+4*size, rh+4*size, GdColor::from_rgb(64,64,64));
197 screen->fill_rect(rx-1*size, ry-1*size, rw+2*size, rh+2*size, GdColor::from_rgb(128,128,128));
198 screen->fill_rect(rx, ry, rw, rh, GdColor::from_rgb(0,0,0));
202 void App::select_file_and_do_command(const char *title, const char *start_dir, const char *glob, bool for_save, const char *defaultname, SmartPtr<Command1Param<std::string> > command_when_successful) {
203 enqueue_command(new PushActivityCommand(this, new SelectFileActivity(this, title, start_dir, glob, for_save, defaultname, command_when_successful)));
207 void App::ask_yesorno_and_do_command(char const *question, const char *yes_answer, char const *no_answer, SmartPtr<Command> command_when_yes, SmartPtr<Command> command_when_no) {
208 enqueue_command(new PushActivityCommand(this, new AskYesNoActivity(this, question, yes_answer, no_answer, command_when_yes, command_when_no)));
212 void App::show_text_and_do_command(char const *title_line, std::string const &text, SmartPtr<Command> command_after_exit) {
213 enqueue_command(new PushActivityCommand(this, new ShowTextActivity(this, title_line, text, command_after_exit)));
217 void App::show_settings(Setting *settings) {
218 enqueue_command(new PushActivityCommand(this, new SettingsActivity(this, settings)));
221 void App::show_message(std::string const &primary, std::string const &secondary, SmartPtr<Command> command_after_exit) {
222 enqueue_command(new PushActivityCommand(this, new MessageActivity(this, primary, secondary, command_after_exit)));
225 void App::show_about_info() {
226 // TRANSLATORS: about dialog box categories.
227 struct String {
228 char const *title;
229 char const *text;
230 } strings[] = { { "GDash", About::comments }, { _("Copyright"), About::copyright}, { _("Website"), About::website }, { NULL, NULL } };
231 // TRANSLATORS: about dialog box categories.
232 struct StringArray {
233 char const *title;
234 char const **texts;
235 } stringarrays[] = {
236 { _("Authors"), About::authors },
237 { _("Artists"), About::artists },
238 { _("Documenters"), About::documenters },
239 { NULL, NULL }
241 std::string text;
243 for (String *s = strings; s->title != NULL; ++s) {
244 text += SPrintf("%c%s\n%c%s\n\n") % GD_COLOR_INDEX_YELLOW % s->title % GD_COLOR_INDEX_LIGHTBLUE % s->text;
246 for (StringArray *s = stringarrays; s->title != NULL; ++s) {
247 text += SPrintf("%c%s%c\n") % GD_COLOR_INDEX_YELLOW % s->title % GD_COLOR_INDEX_LIGHTBLUE;
248 for (char const **t = s->texts; *t != NULL; ++t) {
249 text += SPrintf("%c%c %c%s\n") % GD_COLOR_INDEX_YELLOW % GD_PLAYER_CHAR % GD_COLOR_INDEX_LIGHTBLUE % *t;
251 text += '\n';
253 // TRANSLATORS: about dialog box categories.
254 text += SPrintf("%c%s\n%c%s") % GD_COLOR_INDEX_YELLOW % _("License") % GD_COLOR_INDEX_LIGHTBLUE % About::license;
256 show_text_and_do_command(PACKAGE_NAME PACKAGE_VERSION, text);
260 void App::input_text_and_do_command(char const *title_line, char const *default_text, SmartPtr<Command1Param<std::string> > command_when_successful) {
261 enqueue_command(new PushActivityCommand(this, new InputTextActivity(this, title_line, default_text, command_when_successful)));
265 /** select color for the next text drawing */
266 void App::set_color(const GdColor &color) {
267 font_manager->set_color(color);
271 /** write something to the screen, with normal characters.
272 * x=-1 -> center horizontally */
273 int App::blittext_n(int x, int y, const char *text) {
274 return font_manager->blittext_n(*screen, x, y, text);
278 /** set status line (the last line in the screen) to text */
279 void App::status_line(const char *text) {
280 font_manager->blittext_n(*screen, -1, screen->get_height()-font_manager->get_font_height(), GD_GDASH_GRAY2, text);
284 /** set title line (the first line in the screen) to text */
285 void App::title_line(const char *text) {
286 font_manager->blittext_n(*screen, -1, 0, GD_GDASH_WHITE, text);
290 void App::timer_event(int ms_elapsed) {
291 if (topmost_activity()) {
292 topmost_activity()->timer_event(ms_elapsed);
293 process_commands();
298 void App::timer2_event() {
299 if (topmost_activity()) {
300 topmost_activity()->timer2_event();
301 process_commands();
306 void App::keypress_event(Activity::KeyCode keycode, int gfxlib_keycode) {
307 /* send it to the gameinput object. */
308 gameinput->keypress(gfxlib_keycode);
310 /* and process it. */
311 switch (keycode) {
312 case F9:
313 /* if we have sound, key f9 will push a volume activity.
314 * that activity will receive the keypresses. */
315 #ifdef HAVE_SDL
316 if (dynamic_cast<VolumeActivity *>(topmost_activity()) == NULL) {
317 enqueue_command(new PushActivityCommand(this, new VolumeActivity(this)));
319 #endif // ifdef HAVE_SDL
320 break;
321 case F10:
322 /* f10 is reserved for the menu in the gtk version. do not pass to the activity,
323 * if we may get this somehow. */
324 break;
325 case F11:
326 /* fullscreen switch. do not pass to the app. */
327 break;
328 default:
329 /* other key. pass to the activity. */
330 if (topmost_activity()) {
331 topmost_activity()->keypress_event(keycode, gfxlib_keycode);
332 process_commands();
334 break;
339 void App::redraw_event() {
340 if (topmost_activity()) {
341 topmost_activity()->redraw_event();
342 process_commands();
347 void App::quit_event() {
348 if (quit_event_command != NULL)
349 quit_event_command->execute();
353 /* process pending commands */
354 void App::process_commands() {
355 while (!command_queue.empty()) {
356 SmartPtr<Command> command = command_queue.front();
357 command_queue.pop();
358 command->execute();
361 if (running_activities.empty() && no_activity_command != NULL) {
362 no_activity_command->execute();
363 no_activity_command.release(); /* process this one only once! so forget the object. */
368 void App::request_restart() {
369 request_restart_command->execute();
373 void App::start_editor() {
374 start_editor_command->execute();
378 /* enqueue a command for executing after the event.
379 * and empty command is allowed to be pushed, and has no effect. */
380 void App::enqueue_command(SmartPtr<Command> the_command) {
381 if (the_command != NULL)
382 command_queue.push(the_command);
386 void App::push_activity(Activity *the_activity) {
387 /* the currently topmost activity will be hidden by the new one */
388 if (topmost_activity()) {
389 topmost_activity()->hidden_event();
391 running_activities.push(the_activity);
392 the_activity->pushed_event();
393 the_activity->shown_event();
394 the_activity->redraw_event();
398 void App::pop_activity() {
399 if (!running_activities.empty()) {
400 Activity *popped_activity = running_activities.top();
401 popped_activity->hidden_event();
402 delete running_activities.top();
403 running_activities.pop();
404 /* send a redraw to the topmost one */
405 if (!running_activities.empty()) {
406 running_activities.top()->shown_event();
407 running_activities.top()->redraw_event();
413 void App::pop_all_activities() {
414 while (topmost_activity())
415 pop_activity();
419 Activity *App::topmost_activity() {
420 if (running_activities.empty())
421 return NULL;
422 return running_activities.top();