20130427
[gdash.git] / src / gtk / gtkui.cpp
blob1070d131e6901b2c3fa1f7c80d7cdae9c37e9006
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
29 #include "fileops/loadfile.hpp"
30 #include "fileops/highscore.hpp"
31 #include "cave/caveset.hpp"
32 #include "misc/logger.hpp"
33 #include "gtk/gtkpixbuf.hpp"
34 #include "gtk/gtkpixbuffactory.hpp"
35 #include "gtk/gtkscreen.hpp"
36 #include "editor/editorcellrenderer.hpp"
37 #include "settings.hpp"
38 #include "gtk/gtkui.hpp"
39 #include "misc/about.hpp"
40 #include "misc/helptext.hpp"
41 #include "misc/autogfreeptr.hpp"
42 #include "misc/util.hpp"
44 /* pixbufs of icons and the like */
45 #include "icons.cpp"
47 static std::string last_folder;
49 void gd_register_stock_icons() {
50 /* a table of icon data (guint8*, static arrays included from icons.h) and stock id. */
51 static struct {
52 const guint8 *data;
53 const char *stock_id;
54 } icons[] = {
55 { cave_editor, GD_ICON_CAVE_EDITOR },
56 { move, GD_ICON_EDITOR_MOVE },
57 { add_join, GD_ICON_EDITOR_JOIN },
58 { add_freehand, GD_ICON_EDITOR_FREEHAND },
59 { add_point, GD_ICON_EDITOR_POINT },
60 { add_line, GD_ICON_EDITOR_LINE },
61 { add_rectangle, GD_ICON_EDITOR_RECTANGLE },
62 { add_filled_rectangle, GD_ICON_EDITOR_FILLRECT },
63 { add_raster, GD_ICON_EDITOR_RASTER },
64 { add_fill_border, GD_ICON_EDITOR_FILL_BORDER },
65 { add_fill_replace, GD_ICON_EDITOR_FILL_REPLACE },
66 { add_maze, GD_ICON_EDITOR_MAZE },
67 { add_maze_uni, GD_ICON_EDITOR_MAZE_UNI },
68 { add_maze_braid, GD_ICON_EDITOR_MAZE_BRAID },
69 { snapshot, GD_ICON_SNAPSHOT },
70 { restart_level, GD_ICON_RESTART_LEVEL },
71 { random_fill, GD_ICON_RANDOM_FILL },
72 { award, GD_ICON_AWARD },
73 { to_top, GD_ICON_TO_TOP },
74 { to_bottom, GD_ICON_TO_BOTTOM },
75 { object_on_all, GD_ICON_OBJECT_ON_ALL },
76 { object_not_on_all, GD_ICON_OBJECT_NOT_ON_ALL },
77 { object_not_on_current, GD_ICON_OBJECT_NOT_ON_CURRENT },
78 { replay, GD_ICON_REPLAY },
79 { keyboard, GD_ICON_KEYBOARD },
80 { image, GD_ICON_IMAGE },
81 { statistics, GD_ICON_STATISTICS },
84 GtkIconFactory *factory = gtk_icon_factory_new();
85 for (unsigned i = 0; i < G_N_ELEMENTS(icons); ++i) {
86 /* 3rd param: copy pixels = false */
87 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_inline(-1, icons[i].data, FALSE, NULL);
88 GtkIconSet *iconset = gtk_icon_set_new_from_pixbuf(pixbuf);
89 g_object_unref(pixbuf);
90 gtk_icon_factory_add(factory, icons[i].stock_id, iconset);
92 gtk_icon_factory_add_default(factory);
93 g_object_unref(factory);
97 GdkPixbuf *gd_icon() {
98 GInputStream *is = g_memory_input_stream_new_from_data(Screen::gdash_icon_48_png, Screen::gdash_icon_48_size, NULL);
99 GError *error = NULL;
100 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_stream(is, NULL, &error);
101 g_object_unref(is);
102 if (error != NULL) {
103 throw std::runtime_error("cannot open inlined icon");
105 return pixbuf;
109 /* return a list of image gtk_image_filter's. */
110 /* they have floating reference. */
111 /* the list is to be freed by the caller. */
112 static GList *image_load_filters() {
113 GSList *formats = gdk_pixbuf_get_formats();
114 GSList *iter;
115 GtkFileFilter *all_filter;
116 GList *filters = NULL; /* new list of filters */
118 all_filter = gtk_file_filter_new();
119 gtk_file_filter_set_name(all_filter, _("All image files"));
121 /* iterate the list of formats given by gdk. create file filters for each. */
122 for (iter = formats; iter != NULL; iter = iter->next) {
123 GdkPixbufFormat *frm = (GdkPixbufFormat *)iter->data;
125 if (!gdk_pixbuf_format_is_disabled(frm)) {
126 GtkFileFilter *filter = gtk_file_filter_new();
127 gtk_file_filter_set_name(filter, gdk_pixbuf_format_get_description(frm));
128 char **extensions = gdk_pixbuf_format_get_extensions(frm);
129 for (int i = 0; extensions[i] != NULL; i++) {
130 std::string pattern = SPrintf("*.%s") % extensions[i];
131 gtk_file_filter_add_pattern(filter, pattern.c_str());
132 gtk_file_filter_add_pattern(all_filter, pattern.c_str());
134 g_strfreev(extensions);
136 filters = g_list_append(filters, filter);
139 g_slist_free(formats);
141 /* add "all image files" filter */
142 filters = g_list_prepend(filters, all_filter);
144 return filters;
148 /* file open dialog, with filters for image types gdk-pixbuf recognizes. */
149 char *gd_select_image_file(const char *title) {
150 GtkWidget *dialog;
151 GList *filters;
152 GList *iter;
153 int result;
154 char *filename;
156 dialog = gtk_file_chooser_dialog_new(title, guess_active_toplevel(), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
157 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
159 /* obtain list of image filters, and add all to the window */
160 filters = image_load_filters();
161 for (iter = filters; iter != NULL; iter = iter->next)
162 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), GTK_FILE_FILTER(iter->data));
163 g_list_free(filters);
165 result = gtk_dialog_run(GTK_DIALOG(dialog));
166 if (result == GTK_RESPONSE_ACCEPT)
167 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
168 else
169 filename = NULL;
170 gtk_widget_destroy(dialog);
172 return filename;
177 * Try to guess which window is active.
179 GtkWindow *guess_active_toplevel() {
180 GtkWidget *parent = NULL;
182 /* before doing anything, process updates, as windows may have been opened or closed right at the previous moment */
183 gdk_window_process_all_updates();
185 /* if we find a modal window, it is active. */
186 GList *toplevels = gtk_window_list_toplevels();
187 for (GList *iter = toplevels; iter != NULL; iter = iter->next)
188 if (gtk_window_get_modal(GTK_WINDOW(iter->data)))
189 parent = (GtkWidget *)iter->data;
191 /* if no modal window found, search for a focused toplevel */
192 if (!parent)
193 for (GList *iter = toplevels; iter != NULL; iter = iter->next)
194 if (gtk_window_has_toplevel_focus(GTK_WINDOW(iter->data)))
195 parent = (GtkWidget *)iter->data;
197 /* if any of them is focused, just choose the last from the list as a fallback. */
198 if (!parent && toplevels)
199 parent = (GtkWidget *) g_list_last(toplevels)->data;
200 g_list_free(toplevels);
202 if (parent)
203 return GTK_WINDOW(parent);
204 else
205 return NULL;
210 * Show a message dialog, with the specified message type (warning, error, info) and texts.
212 * @param type GtkMessageType - sets icon to show.
213 * @param primary Primary text.
214 * @param secondary Secondary (small) text - may be null.
216 static void show_message(GtkMessageType type, const char *primary, const char *secondary) {
217 GtkWidget *dialog = gtk_message_dialog_new(guess_active_toplevel(),
218 GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
219 type, GTK_BUTTONS_OK,
220 "%s", primary);
221 gtk_window_set_title(GTK_WINDOW(dialog), "GDash");
222 /* secondary message exists an is not empty string: */
223 if (secondary && secondary[0] != 0)
224 gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
225 gtk_dialog_run(GTK_DIALOG(dialog));
226 gtk_widget_destroy(dialog);
230 void gd_warningmessage(const char *primary, const char *secondary) {
231 show_message(GTK_MESSAGE_WARNING, primary, secondary);
235 void gd_errormessage(const char *primary, const char *secondary) {
236 show_message(GTK_MESSAGE_ERROR, primary, secondary);
240 void gd_infomessage(const char *primary, const char *secondary) {
241 show_message(GTK_MESSAGE_INFO, primary, secondary);
246 * If necessary, ask the user if he doesn't want to save changes to cave.
248 * If the caveset has no modification, this function simply returns true.
250 bool gd_discard_changes(CaveSet const &caveset) {
251 /* save highscore on every ocassion when the caveset is to be removed from memory */
252 save_highscore(caveset);
254 /* caveset is not edited, so pretend user confirmed */
255 if (!caveset.edited)
256 return TRUE;
258 GtkWidget *dialog = gtk_message_dialog_new(guess_active_toplevel(), GtkDialogFlags(0), GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Caveset \"%s\" is edited or new replays are added. Discard changes?"), caveset.name.c_str());
259 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), _("If you discard the caveset, all changes and new replays will be lost."));
260 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
261 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
262 /* create a discard button with a trash icon and Discard text */
263 GtkWidget *button = gtk_button_new_with_mnemonic(_("_Discard"));
264 gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
265 gtk_widget_show(button);
266 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_YES);
268 bool discard = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES;
269 gtk_widget_destroy(dialog);
271 /* return button pressed */
272 return discard;
276 /* file operation was successful, put it into the recent manager */
277 static void caveset_file_operation_successful(const char *filename) {
278 AutoGFreePtr<char> uri(g_filename_to_uri(filename, NULL, NULL));
279 gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri);
284 * Save caveset to specified directory, and pop up error message if failed.
286 static void caveset_save(const gchar *filename, CaveSet &caveset) {
287 try {
288 caveset.save_to_file(filename);
289 caveset_file_operation_successful(filename);
290 } catch (std::exception &e) {
291 gd_errormessage(e.what(), filename);
297 * Pops up a "save file as" dialog to the user, to select a file to save the caveset to.
298 * If selected, saves the file.
300 * @param parent Parent window for the dialog.
301 * @param caveset The caveset to save.
303 void gd_save_caveset_as(CaveSet &caveset) {
304 GtkWidget *dialog = gtk_file_chooser_dialog_new(_("Save File As"), guess_active_toplevel(), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
305 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
306 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
308 GtkFileFilter *filter = gtk_file_filter_new();
309 gtk_file_filter_set_name(filter, _("BDCFF cave sets (*.bd)"));
310 gtk_file_filter_add_pattern(filter, "*.bd");
311 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
312 filter = gtk_file_filter_new();
313 gtk_file_filter_set_name(filter, _("All files (*)"));
314 gtk_file_filter_add_pattern(filter, "*");
315 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
317 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), CPrintf("%s.bd") % caveset.name);
319 std::string filename;
320 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
321 filename = gd_tostring_free(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
322 gtk_widget_destroy(dialog);
324 /* if we have a filename, do the save */
325 if (filename != "") {
326 /* if it has no .bd extension, add one */
327 if (!g_str_has_suffix(filename.c_str(), ".bd"))
328 filename = SPrintf("%s.bd") % filename;
329 caveset_save(filename.c_str(), caveset);
335 * Save the current caveset. If no filename is stored, asks the user for a new filename before proceeding.
337 * @param parent Parent window for dialogs.
338 * @param caveset Caveset to save.
340 void gd_save_caveset(CaveSet &caveset) {
341 if (caveset.filename == "")
342 /* if no filename remembered, rather start the save_as function, which asks for one. */
343 gd_save_caveset_as(caveset);
344 else
345 /* if given, save. */
346 caveset_save(caveset.filename.c_str(), caveset);
351 * Pops up a file selection dialog; and loads the caveset selected.
353 * Before doing anything, asks the user if he wants to save the current caveset.
354 * If it is edited and not saved, this function will do nothing.
356 void gd_open_caveset(const char *directory, CaveSet &caveset) {
357 /* if caveset is edited, and user does not want to discard changes */
358 if (!gd_discard_changes(caveset))
359 return;
361 GtkWidget *dialog = gtk_file_chooser_dialog_new(_("Open File"), guess_active_toplevel(), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
362 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
364 GtkFileFilter *filter = gtk_file_filter_new();
365 gtk_file_filter_set_name(filter, _("GDash cave sets"));
366 for (int i = 0; gd_caveset_extensions[i] != NULL; i++)
367 gtk_file_filter_add_pattern(filter, gd_caveset_extensions[i]);
368 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
370 /* if callback shipped with a directory name, show that directory by default */
371 if (directory)
372 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), directory);
373 else if (last_folder != "")
374 /* if we previously had an open command, the directory was remembered */
375 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), last_folder.c_str());
376 else
377 /* otherwise user home */
378 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), g_get_home_dir());
380 int result = gtk_dialog_run(GTK_DIALOG(dialog));
381 std::string filename;
382 if (result == GTK_RESPONSE_ACCEPT) {
383 filename = gd_tostring_free(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
384 last_folder = gd_tostring_free(gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog)));
387 /* WINDOWS GTK+ 20080926 HACK */
388 /* gtk bug - sometimes the above widget destroy creates an error message. */
389 /* so we delete the error flag here. */
390 /* MacOS GTK+ hack */
391 /* well, in MacOS, the file open dialog does report this error: */
392 /* "Unable to find default local directory monitor type" - original text */
393 /* "Vorgegebener Überwachungstyp für lokale Ordner konnte nicht gefunden werden" - german text */
394 /* so better to always clear the error flag. */
396 Logger l;
397 gtk_widget_destroy(dialog);
398 l.clear();
401 /* if got a filename, load the file */
402 if (filename != "") {
403 try {
404 caveset = load_caveset_from_file(filename.c_str());
405 } catch (std::exception &e) {
406 gd_errormessage(_("Error loading caveset."), e.what());
413 * Convenience function to create a label with centered text.
414 * @param markup The text to show (in pango markup format)
415 * @return The new GtkLabel.
417 GtkWidget *gd_label_new_centered(const char *markup) {
418 return gtk_widget_new(GTK_TYPE_LABEL, "label", markup, "use-markup", TRUE, "xalign", (double) 0.5, NULL);
423 * Convenience function to create a label with left aligned text.
424 * @param markup The text to show (in pango markup format)
425 * @return The new GtkLabel.
427 GtkWidget *gd_label_new_leftaligned(const char *markup) {
428 return gtk_widget_new(GTK_TYPE_LABEL, "label", markup, "use-markup", TRUE, "xalign", (double) 0.0, NULL);
433 * Convenience function to create a label with right aligned text.
434 * @param markup The text to show (in pango markup format)
435 * @return The new GtkLabel.
437 GtkWidget *gd_label_new_rightaligned(const char *markup) {
438 return gtk_widget_new(GTK_TYPE_LABEL, "label", markup, "use-markup", TRUE, "xalign", (double) 1.0, NULL);
442 void gd_show_errors(Logger &l, const char *title, bool always_show) {
443 if (l.get_messages().empty() && !always_show)
444 return;
445 /* create text buffer */
446 GtkTextIter iter;
447 GdkPixbuf *pixbuf_error, *pixbuf_warning, *pixbuf_info;
449 GtkWidget *dialog = gtk_dialog_new_with_buttons(title, guess_active_toplevel(), GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
450 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
451 gtk_window_set_default_size(GTK_WINDOW(dialog), 512, 384);
452 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
453 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
454 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
455 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
457 /* get text and show it */
458 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
459 GtkWidget *view = gtk_text_view_new_with_buffer(buffer);
460 gtk_container_add(GTK_CONTAINER(sw), view);
461 g_object_unref(buffer);
463 pixbuf_error = gtk_widget_render_icon(view, GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU, NULL);
464 pixbuf_warning = gtk_widget_render_icon(view, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU, NULL);
465 pixbuf_info = gtk_widget_render_icon(view, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_MENU, NULL);
466 Logger::Container const &messages = l.get_messages();
467 for (Logger::ConstIterator error = messages.begin(); error != messages.end(); ++error) {
468 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
469 if (error->sev <= ErrorMessage::Message)
470 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_info);
471 else if (error->sev <= ErrorMessage::Warning)
472 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_warning);
473 else
474 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_error);
475 gtk_text_buffer_insert(buffer, &iter, error->message.c_str(), -1);
476 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
478 g_object_unref(pixbuf_error);
479 g_object_unref(pixbuf_warning);
480 g_object_unref(pixbuf_info);
482 /* set some tags */
483 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
484 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
485 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(view), 3);
486 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 6);
487 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 6);
488 gtk_widget_show_all(dialog);
489 gtk_dialog_run(GTK_DIALOG(dialog));
490 gtk_widget_destroy(dialog);
492 /* shown to the users - forget them. */
493 l.clear();
498 * Creates a small dialog window with the given text and asks the user a yes/no question
499 * @param primary The primary (upper) text to show in the dialog.
500 * @param secondary The secondary (lower) text to show in the dialog. May be NULL.
501 * @return true, if the user answered yes, no otherwise.
503 bool gd_question_yesno(const char *primary, const char *secondary) {
504 GtkWidget *dialog = gtk_message_dialog_new(guess_active_toplevel(), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", primary);
505 if (secondary && !g_str_equal(secondary, ""))
506 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
507 int response = gtk_dialog_run(GTK_DIALOG(dialog));
508 gtk_widget_destroy(dialog);
510 return response == GTK_RESPONSE_YES;
515 * Adds a hint (text) to the lower part of the dialog.
516 * Also adds a little INFO icon.
517 * Turns off the separator of the dialog, as it looks nicer without.
519 void gd_dialog_add_hint(GtkDialog *dialog, const char *hint) {
520 /* turn off separator, as it does not look nice with the hint */
521 gtk_dialog_set_has_separator(dialog, FALSE);
523 GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0);
524 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox), align, FALSE, TRUE, 0);
525 GtkWidget *hbox = gtk_hbox_new(FALSE, 6);
526 gtk_container_add(GTK_CONTAINER(align), hbox);
527 GtkWidget *label = gd_label_new_centered(hint);
528 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
529 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG), FALSE, FALSE, 0);
530 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
534 void gd_show_about_info() {
535 gtk_show_about_dialog(guess_active_toplevel(), "program-name", "GDash", "license", About::license, "wrap-license", TRUE, "copyright", About::copyright, "authors", About::authors, "version", PACKAGE_VERSION, "comments", _(About::comments), "translator-credits", _(About::translator_credits), "website", About::website, "artists", About::artists, "documenters", About::documenters, NULL);
544 * Opens a dialog, containing help text.
545 * Waits for the user to close dialog.
547 * @param help_text The paragraphs of the help text.
548 * @param parent Parent widget of the dialog box.
550 void show_help_window(const helpdata help_text[], GtkWidget *parent) {
551 GTKPixbufFactory pf;
552 GTKScreen screen(pf, NULL);
553 EditorCellRenderer cr(screen, gd_theme);
555 /* create text buffer */
556 GtkTextIter iter;
557 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
558 gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0);
559 gtk_text_buffer_create_tag(buffer, "name", "weight", PANGO_WEIGHT_BOLD, "scale", PANGO_SCALE_LARGE, NULL);
560 gtk_text_buffer_create_tag(buffer, "bold", "weight", PANGO_WEIGHT_BOLD, "scale", PANGO_SCALE_MEDIUM, NULL);
561 for (unsigned int i = 0; g_strcmp0(help_text[i].stock_id, HELP_LAST_LINE) != 0; ++i) {
562 if (help_text[i].stock_id) {
563 GdkPixbuf *pixbuf = gtk_widget_render_icon(parent, help_text[i].stock_id, GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
564 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
565 gtk_text_buffer_insert(buffer, &iter, " ", -1);
566 g_object_unref(pixbuf);
569 GdElementEnum element = help_text[i].element;
570 if (element != O_NONE) {
571 gtk_text_buffer_insert_pixbuf(buffer, &iter, cr.combo_pixbuf_simple(element));
572 gtk_text_buffer_insert(buffer, &iter, " ", -1);
573 if (help_text[i].heading == NULL) {
574 /* add element name only if no other text given */
575 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, visible_name_no_attribute(element).c_str(), -1, "name", NULL);
576 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
579 /* some words in big letters */
580 if (help_text[i].heading) {
581 if (element == O_NONE && i != 0 && help_text[i].stock_id == NULL)
582 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
583 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _(help_text[i].heading), -1, "name", NULL);
584 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
586 /* keyboard stuff in bold */
587 if (help_text[i].keyname) {
588 gtk_text_buffer_insert(buffer, &iter, " ", -1);
589 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _(help_text[i].keyname), -1, "bold", NULL);
590 gtk_text_buffer_insert(buffer, &iter, "\t", -1);
592 if (help_text[i].description) {
593 /* the long text */
594 gtk_text_buffer_insert(buffer, &iter, gettext(help_text[i].description), -1);
595 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
599 // TRANSLATORS: Title text capitalization in English
600 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("GDash Help"), GTK_WINDOW(parent), GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
601 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
602 gtk_window_set_default_size(GTK_WINDOW(dialog), 512, 384);
603 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
604 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
605 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
606 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
608 /* get text and show it */
609 GtkWidget *view = gtk_text_view_new_with_buffer(buffer);
610 g_object_unref(buffer);
611 gtk_container_add(GTK_CONTAINER(sw), view);
613 /* set some tags */
614 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
615 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
616 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
617 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(view), 3);
618 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 6);
619 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 6);
620 PangoTabArray *tabarray = pango_tab_array_new_with_positions(5, FALSE,
621 PANGO_TAB_LEFT, 10 * 7 * PANGO_SCALE,
622 PANGO_TAB_LEFT, 20 * 7 * PANGO_SCALE,
623 PANGO_TAB_LEFT, 30 * 7 * PANGO_SCALE,
624 PANGO_TAB_LEFT, 40 * 7 * PANGO_SCALE,
625 PANGO_TAB_LEFT, 50 * 7 * PANGO_SCALE);
626 gtk_text_view_set_tabs(GTK_TEXT_VIEW(view), tabarray);
627 pango_tab_array_free(tabarray);
629 gtk_widget_show_all(dialog);
630 gtk_dialog_run(GTK_DIALOG(dialog));
631 gtk_widget_destroy(dialog);