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.
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
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
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.
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.
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
) {
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];
144 pixbuf_factory
= NULL
;
149 background_image
= NULL
;
154 pop_all_activities();
155 delete background_image
;
159 delete pixbuf_factory
;
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);
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.
230 } strings
[] = { { "GDash", About::comments
}, { _("Copyright"), About::copyright
}, { _("Website"), About::website
}, { NULL
, NULL
} };
231 // TRANSLATORS: about dialog box categories.
236 { _("Authors"), About::authors
},
237 { _("Artists"), About::artists
},
238 { _("Documenters"), About::documenters
},
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
;
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
);
298 void App::timer2_event() {
299 if (topmost_activity()) {
300 topmost_activity()->timer2_event();
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. */
313 /* if we have sound, key f9 will push a volume activity.
314 * that activity will receive the keypresses. */
316 if (dynamic_cast<VolumeActivity
*>(topmost_activity()) == NULL
) {
317 enqueue_command(new PushActivityCommand(this, new VolumeActivity(this)));
319 #endif // ifdef HAVE_SDL
322 /* f10 is reserved for the menu in the gtk version. do not pass to the activity,
323 * if we may get this somehow. */
326 /* fullscreen switch. do not pass to the app. */
329 /* other key. pass to the activity. */
330 if (topmost_activity()) {
331 topmost_activity()->keypress_event(keycode
, gfxlib_keycode
);
339 void App::redraw_event() {
340 if (topmost_activity()) {
341 topmost_activity()->redraw_event();
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();
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())
419 Activity
*App::topmost_activity() {
420 if (running_activities
.empty())
422 return running_activities
.top();