Fix division by zero when unit activity rate is zero
[freeciv.git] / tools / mpgui_gtk3.c
blobdd1d315871875e2296d80633d59570a22b659ae6
1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 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 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <stdlib.h>
20 #include <gtk/gtk.h>
22 /* utility */
23 #include "fc_cmdline.h"
24 #include "fciconv.h"
25 #include "fcintl.h"
26 #include "log.h"
27 #include "mem.h"
29 /* common */
30 #include "version.h"
32 /* modinst */
33 #include "download.h"
34 #include "mpcmdline.h"
35 #include "mpdb.h"
37 #include "modinst.h"
39 static GtkWidget *statusbar;
40 static GtkWidget *progressbar;
41 static GtkWidget *main_list;
42 static GtkListStore *main_store;
43 static GtkWidget *URL_input;
44 static gboolean downloading = FALSE;
46 struct fcmp_params fcmp = {
47 .list_url = MODPACK_LIST_URL,
48 .inst_prefix = NULL,
49 .autoinstall = NULL
52 static gboolean quit_dialog_callback(void);
54 #define ML_COL_NAME 0
55 #define ML_COL_VER 1
56 #define ML_COL_INST 2
57 #define ML_COL_TYPE 3
58 #define ML_COL_SUBTYPE 4
59 #define ML_COL_LIC 5
60 #define ML_COL_URL 6
62 #define ML_COL_COUNT 7
64 #define ML_TYPE 7
65 #define ML_NOTES 8
66 #define ML_STORE_SIZE 9
68 /****************************************************************
69 freeciv-modpack quit
70 ****************************************************************/
71 static void modinst_quit(void)
73 save_install_info_lists(&fcmp);
75 fcmp_deinit();
76 cmdline_option_values_free();
78 exit(EXIT_SUCCESS);
81 /****************************************************************
82 This is the response callback for the dialog with the message:
83 Are you sure you want to quit?
84 ****************************************************************/
85 static void quit_dialog_response(GtkWidget *dialog, gint response)
87 gtk_widget_destroy(dialog);
88 if (response == GTK_RESPONSE_YES) {
89 modinst_quit();
93 /****************************************************************
94 Popups the quit dialog if
95 ****************************************************************/
96 static gboolean quit_dialog_callback(void)
98 if (downloading) {
99 /* Download in progress. Confirm quit from user. */
100 static GtkWidget *dialog;
102 if (!dialog) {
103 dialog = gtk_message_dialog_new(NULL,
105 GTK_MESSAGE_WARNING,
106 GTK_BUTTONS_YES_NO,
107 _("Modpack installation in progress.\nAre you sure you want to quit?"));
109 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
111 g_signal_connect(dialog, "response",
112 G_CALLBACK(quit_dialog_response), NULL);
113 g_signal_connect(dialog, "destroy",
114 G_CALLBACK(gtk_widget_destroyed), &dialog);
117 gtk_window_present(GTK_WINDOW(dialog));
119 } else {
120 /* User loses no work by quitting, so let's not annoy him/her
121 * with confirmation dialog. */
122 modinst_quit();
125 /* Stop emission of event. */
126 return TRUE;
129 /**************************************************************************
130 Progress indications from downloader
131 **************************************************************************/
132 static void msg_callback(const char *msg)
134 log_verbose("%s", msg);
135 gtk_label_set_text(GTK_LABEL(statusbar), msg);
138 /**************************************************************************
139 Progress indications from downloader
140 **************************************************************************/
141 static void pbar_callback(int downloaded, int max)
143 /* Control file already downloaded */
144 double fraction = (double) downloaded / (max);
146 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), fraction);
149 struct msg_data {
150 const char *msg;
153 /**************************************************************************
154 Main thread handling of message sent from downloader thread.
155 **************************************************************************/
156 static gboolean msg_main_thread(gpointer user_data)
158 struct msg_data *data = (struct msg_data *)user_data;
160 msg_callback(data->msg);
162 FC_FREE(data);
164 return G_SOURCE_REMOVE;
167 /**************************************************************************
168 Downloader thread message callback.
169 **************************************************************************/
170 static void msg_dl_thread(const char *msg)
172 struct msg_data *data = fc_malloc(sizeof(*data));
174 data->msg = msg;
176 gdk_threads_add_idle(msg_main_thread, data);
179 struct pbar_data {
180 int current;
181 int max;
184 /**************************************************************************
185 Main thread handling of progressbar update sent from downloader thread.
186 **************************************************************************/
187 static gboolean pbar_main_thread(gpointer user_data)
189 struct pbar_data *data = (struct pbar_data *)user_data;
191 pbar_callback(data->current, data->max);
193 FC_FREE(data);
195 return G_SOURCE_REMOVE;
198 /**************************************************************************
199 Downloader thread progress bar callback.
200 **************************************************************************/
201 static void pbar_dl_thread(int current, int max)
203 struct pbar_data *data = fc_malloc(sizeof(*data));
205 data->current = current;
206 data->max = max;
208 gdk_threads_add_idle(pbar_main_thread, data);
211 /**************************************************************************
212 Main thread handling of versionlist update requested by downloader thread
213 **************************************************************************/
214 static gboolean versionlist_update_main_thread(gpointer user_data)
216 GtkTreeIter iter;
218 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(main_store), &iter)) {
219 do {
220 const char *name_str;
221 int type_int;
222 const char *new_inst;
223 enum modpack_type type;
225 gtk_tree_model_get(GTK_TREE_MODEL(main_store), &iter,
226 ML_COL_NAME, &name_str,
227 ML_TYPE, &type_int,
228 -1);
230 type = type_int;
232 new_inst = get_installed_version(name_str, type);
234 if (new_inst == NULL) {
235 new_inst = _("Not installed");
238 gtk_list_store_set(main_store, &iter,
239 ML_COL_INST, new_inst,
240 -1);
242 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(main_store), &iter));
245 return G_SOURCE_REMOVE;
248 /**************************************************************************
249 Downloader thread requests versionlist update.
250 **************************************************************************/
251 static void versionlist_update_dl_thread(void)
253 gdk_threads_add_idle(versionlist_update_main_thread, NULL);
256 /**************************************************************************
257 Entry point for downloader thread
258 **************************************************************************/
259 static gpointer download_thread(gpointer data)
261 const char *errmsg;
263 errmsg = download_modpack(data, &fcmp, msg_dl_thread, pbar_dl_thread);
265 if (errmsg == NULL) {
266 msg_dl_thread(_("Ready"));
267 } else {
268 msg_dl_thread(errmsg);
271 free(data);
273 versionlist_update_dl_thread();
275 downloading = FALSE;
277 return NULL;
280 /**************************************************************************
281 Download modpack, display error message dialogs
282 **************************************************************************/
283 static void gui_download_modpack(const char *URL)
285 GThread *downloader;
286 char *URLbuf;
288 if (downloading) {
289 gtk_label_set_text(GTK_LABEL(statusbar),
290 _("Another download already active"));
291 return;
294 downloading = TRUE;
296 URLbuf = fc_malloc(strlen(URL) + 1);
298 strcpy(URLbuf, URL);
300 downloader = g_thread_new("Downloader", download_thread, URLbuf);
301 if (downloader == NULL) {
302 gtk_label_set_text(GTK_LABEL(statusbar),
303 _("Failed to start downloader"));
304 free(URLbuf);
305 } else {
306 g_thread_unref(downloader);
310 /**************************************************************************
311 Install modpack button clicked
312 **************************************************************************/
313 static void install_clicked(GtkWidget *w, gpointer data)
315 GtkEntry *URL_in = data;
316 const char *URL = gtk_entry_get_text(URL_in);
318 gui_download_modpack(URL);
321 /**************************************************************************
322 URL entered
323 **************************************************************************/
324 static void URL_return(GtkEntry *w, gpointer data)
326 const char *URL;
328 URL = gtk_entry_get_text(w);
329 gui_download_modpack(URL);
332 /**************************************************************************
333 Callback for getting main list row tooltip
334 **************************************************************************/
335 static gboolean query_main_list_tooltip_cb(GtkWidget *widget,
336 gint x, gint y,
337 gboolean keyboard_tip,
338 GtkTooltip *tooltip,
339 gpointer data)
341 GtkTreeIter iter;
342 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
343 GtkTreeModel *model;
344 const char *notes;
346 if (!gtk_tree_view_get_tooltip_context(tree_view, &x, &y,
347 keyboard_tip,
348 &model, NULL, &iter)) {
349 return FALSE;
352 gtk_tree_model_get(model, &iter,
353 ML_NOTES, &notes,
354 -1);
356 if (notes != NULL) {
357 gtk_tooltip_set_markup(tooltip, notes);
359 return TRUE;
362 return FALSE;
365 /**************************************************************************
366 Build main modpack list view
367 **************************************************************************/
368 static void setup_modpack_list(const char *name, const char *URL,
369 const char *version, const char *license,
370 enum modpack_type type, const char *subtype,
371 const char *notes)
373 GtkTreeIter iter;
374 const char *type_str;
375 const char *lic_str;
376 const char *inst_str;
378 if (modpack_type_is_valid(type)) {
379 type_str = _(modpack_type_name(type));
380 } else {
381 /* TRANS: Unknown modpack type */
382 type_str = _("?");
385 if (license != NULL) {
386 lic_str = license;
387 } else {
388 /* TRANS: License of modpack is not known */
389 lic_str = Q_("?license:Unknown");
392 inst_str = get_installed_version(name, type);
393 if (inst_str == NULL) {
394 inst_str = _("Not installed");
397 gtk_list_store_append(main_store, &iter);
398 gtk_list_store_set(main_store, &iter,
399 ML_COL_NAME, name,
400 ML_COL_VER, version,
401 ML_COL_INST, inst_str,
402 ML_COL_TYPE, type_str,
403 ML_COL_SUBTYPE, subtype,
404 ML_COL_LIC, lic_str,
405 ML_COL_URL, URL,
406 ML_TYPE, type,
407 ML_NOTES, notes,
408 -1);
411 /**************************************************************************
412 Callback called when entry from main modpack list selected
413 **************************************************************************/
414 static void select_from_list(GtkTreeSelection *select, gpointer data)
416 GtkTreeModel *model;
417 GtkTreeIter it;
418 const char *URL;
420 if (!gtk_tree_selection_get_selected(select, &model, &it)) {
421 return;
424 gtk_tree_model_get(model, &it, ML_COL_URL, &URL, -1);
426 gtk_entry_set_text(GTK_ENTRY(URL_input), URL);
429 /**************************************************************************
430 Build widgets
431 **************************************************************************/
432 static void modinst_setup_widgets(GtkWidget *toplevel)
434 GtkWidget *mbox, *Ubox;
435 GtkWidget *version_label;
436 GtkWidget *install_button, *install_label;
437 GtkWidget *URL_label;
438 GtkCellRenderer *renderer;
439 GtkTreeSelection *selection;
440 const char *errmsg;
441 char verbuf[2048];
442 const char *rev_ver = fc_svn_revision();
444 mbox = gtk_grid_new();
445 gtk_orientable_set_orientation(GTK_ORIENTABLE(mbox),
446 GTK_ORIENTATION_VERTICAL);
447 gtk_grid_set_row_spacing(GTK_GRID(mbox), 4);
449 if (rev_ver == NULL) {
450 rev_ver = fc_git_revision();
452 if (rev_ver == NULL) {
453 fc_snprintf(verbuf, sizeof(verbuf), "%s%s", word_version(), VERSION_STRING);
454 } else {
455 fc_snprintf(verbuf, sizeof(verbuf), _("%s%s\ncommit: %s"),
456 word_version(), VERSION_STRING, rev_ver);
458 } else {
459 fc_snprintf(verbuf, sizeof(verbuf), "%s%s (%s)", word_version(), VERSION_STRING,
460 rev_ver);
463 version_label = gtk_label_new(verbuf);
465 main_list = gtk_tree_view_new();
466 renderer = gtk_cell_renderer_text_new();
467 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
468 ML_COL_NAME,
469 _("Name"), renderer, "text", 0,
470 NULL);
471 renderer = gtk_cell_renderer_text_new();
472 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
473 ML_COL_VER,
474 _("Version"), renderer, "text", 1,
475 NULL);
476 renderer = gtk_cell_renderer_text_new();
477 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
478 ML_COL_INST,
479 _("Installed"), renderer, "text", 2,
480 NULL);
481 renderer = gtk_cell_renderer_text_new();
482 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
483 ML_COL_TYPE,
484 Q_("?modpack:Type"),
485 renderer, "text", 3,
486 NULL);
487 renderer = gtk_cell_renderer_text_new();
488 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
489 ML_COL_SUBTYPE,
490 _("Subtype"),
491 renderer, "text", 4,
492 NULL);
493 renderer = gtk_cell_renderer_text_new();
494 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
495 ML_COL_LIC,
496 /* TRANS: noun */
497 _("License"), renderer, "text", 5,
498 NULL);
499 renderer = gtk_cell_renderer_text_new();
500 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(main_list),
501 ML_COL_URL,
502 _("URL"), renderer, "text", 6,
503 NULL);
504 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_list));
505 g_signal_connect(selection, "changed", G_CALLBACK(select_from_list), NULL);
507 install_button = gtk_button_new();
508 install_label = gtk_label_new(_("Install modpack"));
509 gtk_label_set_mnemonic_widget(GTK_LABEL(install_label), install_button);
510 g_object_set_data(G_OBJECT(install_button), "label", install_label);
511 gtk_container_add(GTK_CONTAINER(install_button), install_label);
513 Ubox = gtk_grid_new();
514 gtk_widget_set_halign(Ubox, GTK_ALIGN_CENTER);
515 gtk_grid_set_column_spacing(GTK_GRID(Ubox), 4);
516 URL_label = gtk_label_new_with_mnemonic(_("Modpack URL"));
518 URL_input = gtk_entry_new();
519 gtk_entry_set_width_chars(GTK_ENTRY(URL_input),
520 strlen(EXAMPLE_URL));
521 gtk_entry_set_text(GTK_ENTRY(URL_input), DEFAULT_URL_START);
522 g_signal_connect(URL_input, "activate",
523 G_CALLBACK(URL_return), NULL);
525 g_signal_connect(install_button, "clicked",
526 G_CALLBACK(install_clicked), URL_input);
528 gtk_container_add(GTK_CONTAINER(Ubox), URL_label);
529 gtk_container_add(GTK_CONTAINER(Ubox), URL_input);
531 progressbar = gtk_progress_bar_new();
533 statusbar = gtk_label_new(_("Select modpack to install"));
535 gtk_widget_set_hexpand(main_list, TRUE);
536 gtk_widget_set_vexpand(main_list, TRUE);
538 gtk_container_add(GTK_CONTAINER(mbox), version_label);
539 gtk_container_add(GTK_CONTAINER(mbox), main_list);
540 gtk_container_add(GTK_CONTAINER(mbox), Ubox);
541 gtk_container_add(GTK_CONTAINER(mbox), install_button);
542 gtk_container_add(GTK_CONTAINER(mbox), progressbar);
543 gtk_container_add(GTK_CONTAINER(mbox), statusbar);
545 gtk_container_add(GTK_CONTAINER(toplevel), mbox);
547 main_store = gtk_list_store_new((ML_STORE_SIZE), G_TYPE_STRING, G_TYPE_STRING,
548 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
549 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT,
550 G_TYPE_STRING);
551 errmsg = download_modpack_list(&fcmp, setup_modpack_list, msg_callback);
552 gtk_tree_view_set_model(GTK_TREE_VIEW(main_list), GTK_TREE_MODEL(main_store));
554 g_object_set(main_list, "has-tooltip", TRUE, NULL);
555 g_signal_connect(main_list, "query-tooltip",
556 G_CALLBACK(query_main_list_tooltip_cb), NULL);
558 g_object_unref(main_store);
560 if (errmsg != NULL) {
561 gtk_label_set_text(GTK_LABEL(statusbar), errmsg);
565 /**************************************************************************
566 Entry point of the freeciv-modpack program
567 **************************************************************************/
568 int main(int argc, char *argv[])
570 GtkWidget *toplevel;
571 int ui_options;
573 fcmp_init();
575 /* This modifies argv! */
576 ui_options = fcmp_parse_cmdline(argc, argv);
578 if (ui_options != -1) {
579 int i;
581 for (i = 1; i <= ui_options; i++) {
582 if (is_option("--help", argv[i])) {
583 fc_fprintf(stderr,
584 _("This modpack installer accepts the standard Gtk command-line options\n"
585 "after '--'. See the Gtk documentation.\n\n"));
587 /* TRANS: No full stop after the URL, could cause confusion. */
588 fc_fprintf(stderr, _("Report bugs at %s\n"), BUG_URL);
590 ui_options = -1;
595 if (ui_options != -1) {
597 load_install_info_lists(&fcmp);
599 /* Process GTK arguments */
600 gtk_init(&ui_options, &argv);
602 toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
604 gtk_widget_realize(toplevel);
605 gtk_widget_set_name(toplevel, "Freeciv-modpack");
606 gtk_window_set_title(GTK_WINDOW(toplevel),
607 _("Freeciv modpack installer (gtk3)"));
609 /* Keep the icon of the executable on Windows */
610 #ifndef WIN32_NATIVE
612 /* Unlike main client, this only works if installed. Ignore any
613 * errors loading the icon. */
614 GError *err;
615 (void) gtk_window_set_icon_from_file(GTK_WINDOW(toplevel), MPICON_PATH,
616 &err);
618 #endif /* WIN32_NATIVE */
620 g_signal_connect(toplevel, "delete_event",
621 G_CALLBACK(quit_dialog_callback), NULL);
623 modinst_setup_widgets(toplevel);
625 gtk_widget_show_all(toplevel);
627 if (fcmp.autoinstall != NULL) {
628 gui_download_modpack(fcmp.autoinstall);
631 gtk_main();
633 save_install_info_lists(&fcmp);
636 fcmp_deinit();
637 cmdline_option_values_free();
639 return EXIT_SUCCESS;