]> git.tdb.fi Git - ext/subsurface.git/blob - main.c
Provide an icon for subsurface.
[ext/subsurface.git] / main.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <time.h>
5
6 #include <gconf/gconf-client.h>
7
8 #include "dive.h"
9 #include "divelist.h"
10 #include "display.h"
11
12 GtkWidget *main_window;
13 GtkWidget *main_vbox;
14 GtkWidget *error_info_bar;
15 GtkWidget *error_label;
16 int        error_count;
17 struct DiveList   dive_list;
18
19 GConfClient *gconf;
20 struct units output_units;
21
22 #define GCONF_NAME(x) "/apps/subsurface/" #x
23
24 static int sortfn(const void *_a, const void *_b)
25 {
26         const struct dive *a = *(void **)_a;
27         const struct dive *b = *(void **)_b;
28
29         if (a->when < b->when)
30                 return -1;
31         if (a->when > b->when)
32                 return 1;
33         return 0;
34 }
35
36 /*
37  * This doesn't really report anything at all. We just sort the
38  * dives, the GUI does the reporting
39  */
40 void report_dives(void)
41 {
42         int i;
43
44         qsort(dive_table.dives, dive_table.nr, sizeof(struct dive *), sortfn);
45
46         for (i = 1; i < dive_table.nr; i++) {
47                 struct dive **pp = &dive_table.dives[i-1];
48                 struct dive *prev = pp[0];
49                 struct dive *dive = pp[1];
50                 struct dive *merged;
51
52                 if (prev->when + prev->duration.seconds < dive->when)
53                         continue;
54
55                 merged = try_to_merge(prev, dive);
56                 if (!merged)
57                         continue;
58
59                 free(prev);
60                 free(dive);
61                 *pp = merged;
62                 dive_table.nr--;
63                 memmove(pp+1, pp+2, sizeof(*pp)*(dive_table.nr - i));
64
65                 /* Redo the new 'i'th dive */
66                 i--;
67         }
68 }
69
70 static void parse_argument(const char *arg)
71 {
72         const char *p = arg+1;
73
74         do {
75                 switch (*p) {
76                 case 'v':
77                         verbose++;
78                         continue;
79                 default:
80                         fprintf(stderr, "Bad argument '%s'\n", arg);
81                         exit(1);
82                 }
83         } while (*++p);
84 }
85
86 static void on_destroy(GtkWidget* w, gpointer data)
87 {
88         gtk_main_quit();
89 }
90
91 static GtkWidget *dive_profile;
92
93 void update_dive(struct dive *new_dive)
94 {
95         static struct dive *buffered_dive;
96         struct dive *old_dive = buffered_dive;
97
98         if (old_dive) {
99                 flush_dive_info_changes(old_dive);
100                 flush_dive_equipment_changes(old_dive);
101                 flush_divelist(&dive_list, old_dive);
102         }
103         if (new_dive) {
104                 show_dive_info(new_dive);
105                 show_dive_equipment(new_dive);
106         }
107         buffered_dive = new_dive;
108 }
109
110 void repaint_dive(void)
111 {
112         update_dive(current_dive);
113         gtk_widget_queue_draw(dive_profile);
114 }
115
116 static char *existing_filename;
117
118 static void on_info_bar_response(GtkWidget *widget, gint response,
119                                  gpointer data)
120 {
121         if (response == GTK_RESPONSE_OK)
122         {
123                 gtk_widget_destroy(widget);
124                 error_info_bar = NULL;
125         }
126 }
127
128 void report_error(GError* error)
129 {
130         if (error == NULL)
131         {
132                 return;
133         }
134         
135         if (error_info_bar == NULL)
136         {
137                 error_count = 1;
138                 error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
139                                                                GTK_RESPONSE_OK,
140                                                                NULL);
141                 g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
142                 gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar),
143                                               GTK_MESSAGE_ERROR);
144                 
145                 error_label = gtk_label_new(error->message);
146                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar));
147                 gtk_container_add(GTK_CONTAINER(container), error_label);
148                 
149                 gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0);
150                 gtk_widget_show_all(main_vbox);
151         }
152         else
153         {
154                 error_count++;
155                 char buffer[256];
156                 snprintf(buffer, sizeof(buffer), "Failed to open %i files.", error_count);
157                 gtk_label_set(GTK_LABEL(error_label), buffer);
158         }
159 }
160
161 static void file_open(GtkWidget *w, gpointer data)
162 {
163         GtkWidget *dialog;
164         dialog = gtk_file_chooser_dialog_new("Open File",
165                 GTK_WINDOW(main_window),
166                 GTK_FILE_CHOOSER_ACTION_OPEN,
167                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
168                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
169                 NULL);
170         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
171
172         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
173                 GSList *filenames;
174                 char *filename;
175                 filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
176                 
177                 GError *error = NULL;
178                 while(filenames != NULL) {
179                         filename = (char *)filenames->data;
180                         parse_xml_file(filename, &error);
181                         if (error != NULL)
182                         {
183                                 report_error(error);
184                                 g_error_free(error);
185                                 error = NULL;
186                         }
187                         
188                         g_free(filename);
189                         filenames = g_slist_next(filenames);
190                 }
191                 g_slist_free(filenames);
192                 report_dives();
193                 dive_list_update_dives(dive_list);
194         }
195         gtk_widget_destroy(dialog);
196 }
197
198 static void file_save(GtkWidget *w, gpointer data)
199 {
200         GtkWidget *dialog;
201         dialog = gtk_file_chooser_dialog_new("Save File",
202                 GTK_WINDOW(main_window),
203                 GTK_FILE_CHOOSER_ACTION_SAVE,
204                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
205                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
206                 NULL);
207         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
208         if (!existing_filename) {
209                 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
210         } else
211                 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), existing_filename);
212
213         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
214                 char *filename;
215                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
216                 save_dives(filename);
217                 g_free(filename);
218         }
219         gtk_widget_destroy(dialog);
220 }
221
222 static void quit(GtkWidget *w, gpointer data)
223 {
224         gtk_main_quit();
225 }
226
227 static void create_radio(GtkWidget *dialog, const char *name, ...)
228 {
229         va_list args;
230         GtkRadioButton *group = NULL;
231         GtkWidget *box, *label;
232
233         box = gtk_hbox_new(TRUE, 10);
234         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), box);
235
236         label = gtk_label_new(name);
237         gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
238
239         va_start(args, name);
240         for (;;) {
241                 int enabled;
242                 const char *name;
243                 GtkWidget *button;
244                 void *callback_fn;
245
246                 name = va_arg(args, char *);
247                 if (!name)
248                         break;
249                 callback_fn = va_arg(args, void *);
250                 enabled = va_arg(args, int);
251
252                 button = gtk_radio_button_new_with_label_from_widget(group, name);
253                 group = GTK_RADIO_BUTTON(button);
254                 gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
255                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled);
256                 g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL);
257         }
258         va_end(args);
259 }
260
261 #define UNITCALLBACK(name, type, value)                         \
262 static void name(GtkWidget *w, gpointer data)                   \
263 {                                                               \
264         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) \
265                 menu_units.type = value;                        \
266 }
267
268 static struct units menu_units;
269
270 UNITCALLBACK(set_meter, length, METERS)
271 UNITCALLBACK(set_feet, length, FEET)
272 UNITCALLBACK(set_bar, pressure, BAR)
273 UNITCALLBACK(set_psi, pressure, PSI)
274 UNITCALLBACK(set_liter, volume, LITER)
275 UNITCALLBACK(set_cuft, volume, CUFT)
276 UNITCALLBACK(set_celsius, temperature, CELSIUS)
277 UNITCALLBACK(set_fahrenheit, temperature, FAHRENHEIT)
278
279 static void unit_dialog(GtkWidget *w, gpointer data)
280 {
281         int result;
282         GtkWidget *dialog;
283
284         menu_units = output_units;
285
286         dialog = gtk_dialog_new_with_buttons("Units",
287                 GTK_WINDOW(main_window),
288                 GTK_DIALOG_DESTROY_WITH_PARENT,
289                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
290                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
291                 NULL);
292
293         create_radio(dialog, "Depth:",
294                 "Meter", set_meter, (output_units.length == METERS),
295                 "Feet",  set_feet, (output_units.length == FEET),
296                 NULL);
297
298         create_radio(dialog, "Pressure:",
299                 "Bar", set_bar, (output_units.pressure == BAR),
300                 "PSI",  set_psi, (output_units.pressure == PSI),
301                 NULL);
302
303         create_radio(dialog, "Volume:",
304                 "Liter",  set_liter, (output_units.volume == LITER),
305                 "CuFt", set_cuft, (output_units.volume == CUFT),
306                 NULL);
307
308         create_radio(dialog, "Temperature:",
309                 "Celsius", set_celsius, (output_units.temperature == CELSIUS),
310                 "Fahrenheit",  set_fahrenheit, (output_units.temperature == FAHRENHEIT),
311                 NULL);
312
313         gtk_widget_show_all(dialog);
314         result = gtk_dialog_run(GTK_DIALOG(dialog));
315         if (result == GTK_RESPONSE_ACCEPT) {
316                 /* Make sure to flush any modified old dive data with old units */
317                 update_dive(NULL);
318                 output_units = menu_units;
319                 update_dive_list_units(&dive_list);
320                 repaint_dive();
321                 gconf_client_set_bool(gconf, GCONF_NAME(feet), output_units.length == FEET, NULL);
322                 gconf_client_set_bool(gconf, GCONF_NAME(psi), output_units.pressure == PSI, NULL);
323                 gconf_client_set_bool(gconf, GCONF_NAME(cuft), output_units.volume == CUFT, NULL);
324                 gconf_client_set_bool(gconf, GCONF_NAME(fahrenheit), output_units.temperature == FAHRENHEIT, NULL);
325         }
326         gtk_widget_destroy(dialog);
327 }
328
329 static void renumber_dives(int nr)
330 {
331         int i;
332
333         for (i = 0; i < dive_table.nr; i++) {
334                 struct dive *dive = dive_table.dives[i];
335                 dive->number = nr + i;
336         }
337 }
338
339 static void renumber_dialog(GtkWidget *w, gpointer data)
340 {
341         int result;
342         GtkWidget *dialog, *frame, *button;
343
344         dialog = gtk_dialog_new_with_buttons("Renumber",
345                 GTK_WINDOW(main_window),
346                 GTK_DIALOG_DESTROY_WITH_PARENT,
347                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
348                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
349                 NULL);
350
351         frame = gtk_frame_new("New starting number");
352         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), frame);
353
354         button = gtk_spin_button_new_with_range(1, 50000, 1);
355         gtk_container_add(GTK_CONTAINER(frame), button);
356
357         gtk_widget_show_all(dialog);
358         result = gtk_dialog_run(GTK_DIALOG(dialog));
359         if (result == GTK_RESPONSE_ACCEPT) {
360                 int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button));
361                 renumber_dives(nr);
362                 repaint_dive();
363         }
364         gtk_widget_destroy(dialog);
365 }
366
367 static GtkActionEntry menu_items[] = {
368         { "FileMenuAction", GTK_STOCK_FILE, "Log", NULL, NULL, NULL},
369         { "OpenFile",       GTK_STOCK_OPEN, NULL,   "<control>O", NULL, G_CALLBACK(file_open) },
370         { "SaveFile",       GTK_STOCK_SAVE, NULL,   "<control>S", NULL, G_CALLBACK(file_save) },
371         { "Print",          GTK_STOCK_PRINT, NULL,  "<control>P", NULL, G_CALLBACK(do_print) },
372         { "Import",         NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) },
373         { "Units",          NULL, "Units",    NULL, NULL, G_CALLBACK(unit_dialog) },
374         { "Renumber",       NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) },
375         { "Quit",           GTK_STOCK_QUIT, NULL,   "<control>Q", NULL, G_CALLBACK(quit) },
376 };
377 static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
378
379 static const gchar* ui_string = " \
380         <ui> \
381                 <menubar name=\"MainMenu\"> \
382                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
383                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
384                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
385                                 <menuitem name=\"Print\" action=\"Print\" /> \
386                                 <separator name=\"Separator1\"/> \
387                                 <menuitem name=\"Import\" action=\"Import\" /> \
388                                 <separator name=\"Separator2\"/> \
389                                 <menuitem name=\"Units\" action=\"Units\" /> \
390                                 <menuitem name=\"Renumber\" action=\"Renumber\" /> \
391                                 <separator name=\"Separator3\"/> \
392                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
393                         </menu> \
394                 </menubar> \
395         </ui> \
396 ";
397
398 static GtkWidget *get_menubar_menu(GtkWidget *window)
399 {
400         GtkActionGroup *action_group = gtk_action_group_new("Menu");
401         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
402
403         GtkUIManager *ui_manager = gtk_ui_manager_new();
404         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
405         GError* error = 0;
406         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
407
408         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
409         GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
410
411         return menu;
412 }
413
414 static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data)
415 {
416         repaint_dive();
417 }
418
419 int main(int argc, char **argv)
420 {
421         int i;
422         GtkWidget *win;
423         GtkWidget *paned;
424         GtkWidget *info_box;
425         GtkWidget *notebook;
426         GtkWidget *dive_info;
427         GtkWidget *equipment;
428         GtkWidget *menubar;
429         GtkWidget *vbox;
430
431         output_units = SI_units;
432         parse_xml_init();
433
434         gtk_init(&argc, &argv);
435
436         g_type_init();
437         gconf = gconf_client_get_default();
438
439         if (gconf_client_get_bool(gconf, GCONF_NAME(feet), NULL))
440                 output_units.length = FEET;
441         if (gconf_client_get_bool(gconf, GCONF_NAME(psi), NULL))
442                 output_units.pressure = PSI;
443         if (gconf_client_get_bool(gconf, GCONF_NAME(cuft), NULL))
444                 output_units.volume = CUFT;
445         if (gconf_client_get_bool(gconf, GCONF_NAME(fahrenheit), NULL))
446                 output_units.temperature = FAHRENHEIT;
447
448         error_info_bar = NULL;
449         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
450         gtk_window_set_icon_from_file(GTK_WINDOW(win), "icon.svg", NULL);
451         g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL);
452         main_window = win;
453
454         vbox = gtk_vbox_new(FALSE, 0);
455         gtk_container_add(GTK_CONTAINER(win), vbox);
456         main_vbox = vbox;
457
458         menubar = get_menubar_menu(win);
459         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
460
461         /* HPane for left the dive list, and right the dive info */
462         paned = gtk_vpaned_new();
463         gtk_box_pack_end(GTK_BOX(vbox), paned, TRUE, TRUE, 0);
464
465         /* Create the actual divelist */
466         dive_list = dive_list_create();
467         gtk_paned_add2(GTK_PANED(paned), dive_list.container_widget);
468
469         /* VBox for dive info, and tabs */
470         info_box = gtk_vbox_new(FALSE, 6);
471         gtk_paned_add1(GTK_PANED(paned), info_box);
472
473         /* Notebook for dive info vs profile vs .. */
474         notebook = gtk_notebook_new();
475         g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL);
476         gtk_box_pack_start(GTK_BOX(info_box), notebook, TRUE, TRUE, 6);
477
478         /* Frame for dive profile */
479         dive_profile = dive_profile_widget();
480         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_profile, gtk_label_new("Dive Profile"));
481
482         /* Frame for extended dive info */
483         dive_info = extended_dive_info_widget();
484         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_info, gtk_label_new("Dive Notes"));
485
486         /* Frame for dive equipment */
487         equipment = equipment_widget();
488         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), equipment, gtk_label_new("Equipment"));
489
490         gtk_widget_set_app_paintable(win, TRUE);
491         gtk_widget_show_all(win);
492         
493         for (i = 1; i < argc; i++) {
494                 const char *a = argv[i];
495
496                 if (a[0] == '-') {
497                         parse_argument(a);
498                         continue;
499                 }
500                 GError *error = NULL;
501                 parse_xml_file(a, &error);
502                 
503                 if (error != NULL)
504                 {
505                         report_error(error);
506                         g_error_free(error);
507                         error = NULL;
508                 }
509         }
510
511         report_dives();
512         dive_list_update_dives(dive_list);
513
514         gtk_main();
515         return 0;
516 }