sat-log-browser.c
1 /* 2 Gpredict: Real-time satellite tracking and orbit prediction program 3 4 Copyright (C) 2001-2017 Alexandru Csete, OZ9AEC. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, visit http://www.fsf.org/ 18 */ 19 #include <gtk/gtk.h> 20 #include <glib/gi18n.h> 21 22 #include "compat.h" 23 #include "sat-log.h" 24 #include "sat-log-browser.h" 25 26 27 /* columns in the message list */ 28 typedef enum { 29 MSG_LIST_COL_TIME = 0, 30 MSG_LIST_COL_LEVEL, 31 MSG_LIST_COL_MSG, 32 MSG_LIST_COL_NUMBER 33 } msg_list_col_t; 34 35 /* data structure to hold one message */ 36 typedef struct { 37 time_t time; /* time stamp */ 38 sat_log_level_t level; /* debug level */ 39 gchar *message; /* the message itself */ 40 } message_t; 41 42 /* Easy access to column titles */ 43 const gchar *MSG_LIST_COL_TITLE[MSG_LIST_COL_NUMBER] = { 44 N_("Time"), 45 N_("Level"), 46 N_("Message") 47 }; 48 49 const gfloat MSG_LIST_COL_TITLE_ALIGN[MSG_LIST_COL_NUMBER] = { 50 0.5, 0.5, 0.0 51 }; 52 53 const gchar *DEBUG_STR[6] = { 54 N_("NONE"), 55 N_("ERROR"), 56 N_("WARNING"), 57 N_("INFO"), 58 N_("DEBUG") 59 }; 60 61 62 extern GtkWidget *app; 63 static gboolean initialised = FALSE; /* Is module initialised? */ 64 65 /* counters */ 66 static guint32 errors = 0; /* Number of error messages */ 67 static guint32 warnings = 0; /* Number of warning messages */ 68 static guint32 infos = 0; /* Number of verbose messages */ 69 static guint32 debugs = 0; /* Number of trace messages */ 70 71 /* summary labels; they need to be accessible at runtime */ 72 static GtkWidget *errorlabel, *warnlabel, *infolabel, *debuglabel, *sumlabel; 73 74 /* The message window itself */ 75 static GtkWidget *window; 76 77 /* the tree view model */ 78 GtkTreeModel *model; 79 80 81 /* load debug file related */ 82 //static void load_debug_file(GtkWidget * parent); 83 //static int read_debug_file(const gchar * filename); 84 //static void clear_message_list(void); 85 86 static void add_debug_message(const gchar * datetime, 87 sat_log_level_t debug_level, 88 const char *message); 89 90 /* 91 * Clear the message list 92 * 93 * Besides clearing the message list, the function also resets 94 * the counters and set the text of the corresponding widgets 95 * to zero. 96 */ 97 static void clear_message_list() 98 { 99 /* clear the meaase list */ 100 gtk_list_store_clear(GTK_LIST_STORE(model)); 101 102 /* reset the counters and text widgets */ 103 errors = 0; 104 warnings = 0; 105 debugs = 0; 106 107 gtk_label_set_text(GTK_LABEL(errorlabel), "0"); 108 gtk_label_set_text(GTK_LABEL(warnlabel), "0"); 109 gtk_label_set_text(GTK_LABEL(infolabel), "0"); 110 gtk_label_set_text(GTK_LABEL(debuglabel), "0"); 111 gtk_label_set_markup(GTK_LABEL(sumlabel), "<b>0</b>"); 112 } 113 114 /* Read contents of debug file. */ 115 static int read_debug_file(const gchar * filename) 116 { 117 GIOChannel *logfile = NULL; /* the log file */ 118 GError *error = NULL; /* error structure */ 119 gint errorcode = 0; /* error code returned by function */ 120 gchar *line; /* line read from file */ 121 gsize length; /* length of line read from file */ 122 gchar **buff; 123 124 125 /* check file and read contents */ 126 if (g_file_test(filename, G_FILE_TEST_EXISTS)) 127 { 128 /* open file */ 129 logfile = g_io_channel_new_file(filename, "r", &error); 130 131 if (logfile) 132 { 133 /* read the file line by line */ 134 while (g_io_channel_read_line(logfile, 135 &line, 136 &length, 137 NULL, NULL) != G_IO_STATUS_EOF) 138 { 139 /* trim line and split it */ 140 line = g_strdelimit(line, "\n", '\0'); 141 142 buff = g_strsplit(line, SAT_LOG_MSG_SEPARATOR, MSG_LIST_COL_NUMBER + 1); // +1 because we used to have a msg source (pre 1.4) 143 144 /* Gpredict 1.3 and earlier had 4 fields: 145 * buff[0]: Date and time 146 * buff[1]: Message source 147 * buff[2]: Message type 148 * buff[3]: Message 149 * As of 1.4 we no longer have message source: 150 * buff[0]: Date and time 151 * buff[1]: Message type 152 * buff[2]: Message 153 * 154 * Gpredicxt 1.4+ will be able to read logs generated by earlier versions 155 * but not the other way around. 156 */ 157 switch (g_strv_length(buff)) 158 { 159 160 case 1: 161 add_debug_message("", SAT_LOG_LEVEL_ERROR, buff[0]); 162 break; 163 164 case 3: 165 /* v1.4 and later */ 166 add_debug_message(buff[0], 167 (guint) g_ascii_strtod(buff[1], NULL), 168 buff[2]); 169 break; 170 171 case 4: 172 /* v1.3 and earlier with message source */ 173 add_debug_message(buff[0], /* buff[1], - used to be message src */ 174 (guint) g_ascii_strtod(buff[2], NULL), 175 buff[3]); 176 break; 177 default: 178 add_debug_message("", SAT_LOG_LEVEL_ERROR, 179 _("Log file is corrupt")); 180 break; 181 } 182 183 /* clean up */ 184 g_free(line); 185 g_strfreev(buff); 186 } 187 188 errorcode = 0; 189 190 /* Close IO channel; don't care about status. 191 Shutdown will flush the stream and close the channel 192 as soon as the reference count is dropped. Order matters! 193 */ 194 g_io_channel_shutdown(logfile, TRUE, NULL); 195 g_io_channel_unref(logfile); 196 197 } 198 else 199 { 200 /* an error occurred */ 201 sat_log_log(SAT_LOG_LEVEL_ERROR, 202 _("%s:%d: Error open debug log (%s)"), 203 __FILE__, __LINE__, error->message); 204 205 g_clear_error(&error); 206 errorcode = 1; 207 } 208 } 209 else 210 { 211 errorcode = 1; 212 } 213 214 return errorcode; 215 } 216 217 /* 218 * Load debug file. 219 * 220 * This function creates the file chooser dialog, which can be used to select 221 * a file containing debug messages. When the dialog returns, the selected 222 * file is checked and, if the file exists, is read line by line. 223 */ 224 static void load_debug_file(GtkWidget * parent) 225 { 226 gchar *confdir; 227 gchar *filename; 228 gchar *title; 229 gint error; /* error code returned by by read_debug_file */ 230 231 232 GtkWidget *dialog; 233 234 /* create file chooser dialog */ 235 dialog = gtk_file_chooser_dialog_new(_("Select Log File"), 236 GTK_WINDOW(parent), 237 GTK_FILE_CHOOSER_ACTION_OPEN, 238 "_Cancel", GTK_RESPONSE_CANCEL, 239 "_Open", GTK_RESPONSE_ACCEPT, NULL); 240 241 confdir = get_user_conf_dir(); 242 filename = g_strconcat(confdir, G_DIR_SEPARATOR_S, "logs", NULL); 243 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), filename); 244 g_free(filename); 245 g_free(confdir); 246 247 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) 248 { 249 clear_message_list(); 250 251 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); 252 253 /* sanity check of filename will be performed 254 in read_debug_file */ 255 error = read_debug_file(filename); 256 if (error == 0) 257 { 258 /* Update title with filename */ 259 title = g_strdup_printf(_("Log Browser: %s"), filename); 260 gtk_window_set_title(GTK_WINDOW(window), title); 261 g_free(title); 262 } 263 else 264 { 265 /* clear filename from title if unable to read file */ 266 gtk_window_set_title(GTK_WINDOW(window), _("Log Browser")); 267 } 268 269 g_free(filename); 270 } 271 272 gtk_widget_destroy(dialog); 273 } 274 275 /* callback function called when a dialog button is clicked */ 276 static void message_window_response(GtkWidget * widget, gint response, 277 gpointer data) 278 { 279 (void)data; 280 281 switch (response) 282 { 283 case GTK_RESPONSE_CLOSE: 284 gtk_widget_destroy(widget); 285 break; 286 case GTK_RESPONSE_YES: 287 load_debug_file(widget); 288 break; 289 case GTK_RESPONSE_NO: 290 clear_message_list(); 291 break; 292 293 default: 294 break; 295 } 296 } 297 298 /* create summary */ 299 static GtkWidget *create_message_summary() 300 { 301 GtkWidget *vbox; 302 GtkWidget *table; /* table containing everything */ 303 GtkWidget *frame; /* surrounding frame */ 304 GtkWidget *label; /* dummy label */ 305 306 /* create labels */ 307 errorlabel = gtk_label_new("0"); 308 g_object_set(errorlabel, "xalign", 1.0f, "yalign", 0.5f, NULL); 309 310 warnlabel = gtk_label_new("0"); 311 g_object_set(warnlabel, "xalign", 1.0f, "yalign", 0.5f, NULL); 312 313 infolabel = gtk_label_new("0"); 314 g_object_set(infolabel, "xalign", 1.0f, "yalign", 0.5f, NULL); 315 316 debuglabel = gtk_label_new("0"); 317 g_object_set(debuglabel, "xalign", 1.0f, "yalign", 0.5f, NULL); 318 319 sumlabel = gtk_label_new(NULL); 320 gtk_label_set_use_markup(GTK_LABEL(sumlabel), TRUE); 321 gtk_label_set_markup(GTK_LABEL(sumlabel), "<b>0</b>"); 322 g_object_set(sumlabel, "xalign", 1.0f, "yalign", 0.5f, NULL); 323 324 /* create table and add widgets */ 325 table = gtk_grid_new(); 326 gtk_grid_set_column_homogeneous(GTK_GRID(table), TRUE); 327 gtk_grid_set_row_homogeneous(GTK_GRID(table), TRUE); 328 gtk_container_set_border_width(GTK_CONTAINER(table), 10); 329 330 label = gtk_label_new(_("Errors")); 331 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL); 332 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1); 333 334 label = gtk_label_new(_("Warnings")); 335 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL); 336 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1); 337 338 label = gtk_label_new(_("Info")); 339 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL); 340 gtk_grid_attach(GTK_GRID(table), label, 0, 2, 1, 1); 341 342 label = gtk_label_new(_("Debug")); 343 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL); 344 gtk_grid_attach(GTK_GRID(table), label, 0, 3, 1, 1); 345 346 gtk_grid_attach(GTK_GRID(table), 347 gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 4, 2, 1); 348 349 label = gtk_label_new(NULL); 350 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL); 351 gtk_label_set_use_markup(GTK_LABEL(label), TRUE); 352 gtk_label_set_markup(GTK_LABEL(label), _("<b>Total</b>")); 353 gtk_grid_attach(GTK_GRID(table), label, 0, 5, 1, 1); 354 355 gtk_grid_attach(GTK_GRID(table), errorlabel, 1, 0, 1, 1); 356 gtk_grid_attach(GTK_GRID(table), warnlabel, 1, 1, 1, 1); 357 gtk_grid_attach(GTK_GRID(table), infolabel, 1, 2, 1, 1); 358 gtk_grid_attach(GTK_GRID(table), debuglabel, 1, 3, 1, 1); 359 gtk_grid_attach(GTK_GRID(table), sumlabel, 1, 5, 1, 1); 360 361 /* frame around the table */ 362 frame = gtk_frame_new(_(" Summary ")); 363 gtk_frame_set_label_align(GTK_FRAME(frame), 0.5, 0.5); 364 gtk_container_add(GTK_CONTAINER(frame), table); 365 366 /* pack frame into vbox so that it doesn't gets stretched vertically */ 367 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 368 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); 369 370 return vbox; 371 } 372 373 /* create tree view model; we actually create a GtkListStore because we are 374 only interested in a flat list. A GtkListStore can be cast to a GtkTreeModel 375 without any problems. 376 */ 377 static GtkTreeModel *create_list_model() 378 { 379 GtkListStore *liststore; 380 381 liststore = gtk_list_store_new(MSG_LIST_COL_NUMBER, G_TYPE_STRING, 382 G_TYPE_STRING, G_TYPE_STRING); 383 384 /*** Fill existing data into the list here ***/ 385 386 return GTK_TREE_MODEL(liststore); 387 } 388 389 static gint message_window_delete(GtkWidget * widget, GdkEvent * event, 390 gpointer data) 391 { 392 (void)widget; 393 (void)event; 394 (void)data; 395 396 /* return FALSE to indicate that message window 397 should be destroyed */ 398 return FALSE; 399 } 400 401 /* callback function called when the dialog window is destroyed */ 402 static void message_window_destroy(GtkWidget * widget, gpointer data) 403 { 404 (void)widget; 405 (void)data; 406 407 /* clean up memory */ 408 /* GSList, ... */ 409 410 initialised = FALSE; 411 } 412 413 /* Create list view */ 414 static GtkWidget *create_message_list() 415 { 416 GtkWidget *treeview; /* high level treev iew widget */ 417 GtkWidget *swin; /* scrolled window containing the tree view */ 418 GtkCellRenderer *renderer; /* cell renderer used to create a column */ 419 GtkTreeViewColumn *column; /* place holder for a tree view column */ 420 421 guint i; 422 423 treeview = gtk_tree_view_new(); 424 425 for (i = 0; i < MSG_LIST_COL_NUMBER; i++) 426 { 427 renderer = gtk_cell_renderer_text_new(); 428 column = 429 gtk_tree_view_column_new_with_attributes(_(MSG_LIST_COL_TITLE[i]), 430 renderer, "text", i, 431 NULL); 432 gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); 433 434 /* only aligns the headers? */ 435 gtk_tree_view_column_set_alignment(column, 436 MSG_LIST_COL_TITLE_ALIGN[i]); 437 } 438 439 /* create tree view model and finalise tree view */ 440 model = create_list_model(); 441 gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model); 442 g_object_unref(model); 443 444 /* treeview is packed into a scroleld window */ 445 swin = gtk_scrolled_window_new(NULL, NULL); 446 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), 447 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); 448 gtk_container_add(GTK_CONTAINER(swin), treeview); 449 450 return swin; 451 } 452 453 /* Initialise message window. 454 * 455 * This function creates the message window and allocates all the internal 456 * data structures. The function should be called when the main program 457 * is initialised. 458 */ 459 void sat_log_browser_open() 460 { 461 GtkWidget *hbox; 462 gchar *fname; 463 gchar *confdir; 464 gchar *title; 465 gint error; /* error code returned by by read_debug_file */ 466 467 if (!initialised) 468 { 469 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); 470 gtk_box_pack_start(GTK_BOX(hbox), create_message_list(), 471 TRUE, TRUE, 0); 472 gtk_box_pack_start(GTK_BOX(hbox), create_message_summary(), 473 FALSE, TRUE, 0); 474 475 /* create dialog window; we use "fake" stock responses to catch user 476 button clicks (save_as and pause) 477 */ 478 window = gtk_dialog_new_with_buttons(_("Log Browser"), 479 GTK_WINDOW(app), 480 GTK_DIALOG_DESTROY_WITH_PARENT, 481 "_Open", GTK_RESPONSE_YES, 482 "Clear", GTK_RESPONSE_NO, 483 "_Close", GTK_RESPONSE_CLOSE, 484 NULL); 485 486 gtk_window_set_default_size(GTK_WINDOW(window), 600, 300); 487 488 gtk_box_pack_start(GTK_BOX 489 (gtk_dialog_get_content_area(GTK_DIALOG(window))), 490 hbox, TRUE, TRUE, 0); 491 492 /* connect response signal */ 493 g_signal_connect(G_OBJECT(window), "response", 494 G_CALLBACK(message_window_response), NULL); 495 496 /* connect delete and destroy signals */ 497 g_signal_connect(G_OBJECT(window), "delete_event", 498 G_CALLBACK(message_window_delete), NULL); 499 g_signal_connect(G_OBJECT(window), "destroy", 500 G_CALLBACK(message_window_destroy), NULL); 501 502 gtk_widget_show_all(window); 503 504 /* read gpredict.log by default */ 505 confdir = get_user_conf_dir(); 506 fname = g_strconcat(confdir, G_DIR_SEPARATOR_S, 507 "logs", G_DIR_SEPARATOR_S, "gpredict.log", NULL); 508 509 error = read_debug_file(fname); 510 511 if (error == 0) 512 { 513 /* update window title if file read cleanly */ 514 title = g_strdup_printf(_("Log Browser: %s"), fname); 515 gtk_window_set_title(GTK_WINDOW(window), title); 516 517 g_free(title); 518 } 519 else 520 { 521 /* Remove filename if file not read */ 522 gtk_window_set_title(GTK_WINDOW(window), _("Log Browser")); 523 } 524 525 g_free(fname); 526 g_free(confdir); 527 528 initialised = TRUE; 529 } 530 } 531 532 /* Add a message to message list */ 533 static void add_debug_message(const gchar * datetime, 534 sat_log_level_t debug_level, const char *message) 535 { 536 guint total; /* totalt number of messages */ 537 gchar *str; /* string to show message count */ 538 GtkTreeIter item; /* new item added to the list store */ 539 540 gtk_list_store_append(GTK_LIST_STORE(model), &item); 541 gtk_list_store_set(GTK_LIST_STORE(model), &item, 542 MSG_LIST_COL_TIME, datetime, 543 MSG_LIST_COL_LEVEL, _(DEBUG_STR[debug_level]), 544 MSG_LIST_COL_MSG, message, -1); 545 546 switch (debug_level) 547 { 548 case SAT_LOG_LEVEL_ERROR: 549 errors++; 550 str = g_strdup_printf("%d", errors); 551 gtk_label_set_text(GTK_LABEL(errorlabel), str); 552 g_free(str); 553 break; 554 case SAT_LOG_LEVEL_WARN: 555 warnings++; 556 str = g_strdup_printf("%d", warnings); 557 gtk_label_set_text(GTK_LABEL(warnlabel), str); 558 g_free(str); 559 break; 560 case SAT_LOG_LEVEL_INFO: 561 infos++; 562 str = g_strdup_printf("%d", infos); 563 gtk_label_set_text(GTK_LABEL(infolabel), str); 564 g_free(str); 565 break; 566 case SAT_LOG_LEVEL_DEBUG: 567 debugs++; 568 str = g_strdup_printf("%d", debugs); 569 gtk_label_set_text(GTK_LABEL(debuglabel), str); 570 g_free(str); 571 break; 572 573 default: 574 break; 575 } 576 577 /* the sum does not have to be updated for each line */ 578 total = errors + warnings + infos + debugs; 579 str = g_strdup_printf("<b>%d</b>", total); 580 gtk_label_set_markup(GTK_LABEL(sumlabel), str); 581 g_free(str); 582 }