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.
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 */
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. */
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
);
100 GdkPixbuf
*pixbuf
= gdk_pixbuf_new_from_stream(is
, NULL
, &error
);
103 throw std::runtime_error("cannot open inlined icon");
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();
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
);
148 /* file open dialog, with filters for image types gdk-pixbuf recognizes. */
149 char *gd_select_image_file(const char *title
) {
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
));
170 gtk_widget_destroy(dialog
);
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 */
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
);
203 return GTK_WINDOW(parent
);
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
,
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 */
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 */
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
) {
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
);
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
))
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 */
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());
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. */
397 gtk_widget_destroy(dialog
);
401 /* if got a filename, load the file */
402 if (filename
!= "") {
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
)
445 /* create text buffer */
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
);
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
);
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. */
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
) {
552 GTKScreen
screen(pf
, NULL
);
553 EditorCellRenderer
cr(screen
, gd_theme
);
555 /* create text buffer */
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
) {
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
);
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
);