logwindow.c (11037B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2019 the Claws Mail team and Hiroyuki Yamamoto 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #include <glib.h> 20 #include <glib/gi18n.h> 21 #include <gdk/gdkkeysyms.h> 22 #include <gtk/gtk.h> 23 24 #include "logwindow.h" 25 #include "utils.h" 26 #include "gtkutils.h" 27 #include "log.h" 28 #include "hooks.h" 29 #include "prefs_common.h" 30 31 static void hide_cb (GtkWidget *widget, 32 LogWindow *logwin); 33 static void size_allocate_cb (GtkWidget *widget, 34 GtkAllocation *allocation, 35 gpointer data); 36 static gboolean log_window_append (gpointer source, 37 gpointer data); 38 static void log_window_clear (GtkWidget *widget, 39 LogWindow *logwin); 40 static void log_window_popup_menu_extend (GtkTextView *textview, 41 GtkMenu *menu, 42 LogWindow *logwin); 43 44 /*! 45 *\brief Save Gtk object size to prefs dataset 46 */ 47 static void size_allocate_cb(GtkWidget *widget, 48 GtkAllocation *allocation, 49 gpointer data) 50 { 51 gint *prefs_logwin_width = NULL; 52 gint *prefs_logwin_height = NULL; 53 LogInstance instance = GPOINTER_TO_INT(data); 54 55 cm_return_if_fail(allocation != NULL); 56 57 get_log_prefs(instance, &prefs_logwin_width, &prefs_logwin_height); 58 cm_return_if_fail(prefs_logwin_width != NULL); 59 cm_return_if_fail(prefs_logwin_height != NULL); 60 61 gtk_window_get_size(GTK_WINDOW(widget), 62 prefs_logwin_width, prefs_logwin_height); 63 } 64 65 LogWindow *log_window_create(LogInstance instance) 66 { 67 LogWindow *logwin; 68 GtkWidget *window; 69 GtkWidget *scrolledwin; 70 GtkWidget *text; 71 GtkTextBuffer *buffer; 72 GtkTextIter iter; 73 static GdkGeometry geometry; 74 gint *prefs_logwin_width = NULL; 75 gint *prefs_logwin_height = NULL; 76 77 debug_print("Creating log window...\n"); 78 79 get_log_prefs(instance, &prefs_logwin_width, &prefs_logwin_height); 80 cm_return_val_if_fail(prefs_logwin_width != NULL, NULL); 81 cm_return_val_if_fail(prefs_logwin_height != NULL, NULL); 82 83 logwin = g_new0(LogWindow, 1); 84 85 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "logwindow"); 86 gtk_window_set_title(GTK_WINDOW(window), get_log_title(instance)); 87 gtk_window_set_resizable(GTK_WINDOW(window), TRUE); 88 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); 89 g_signal_connect(G_OBJECT(window), "delete_event", 90 G_CALLBACK(gtk_widget_hide_on_delete), NULL); 91 g_signal_connect_after(G_OBJECT(window), "hide", 92 G_CALLBACK(hide_cb), logwin); 93 gtk_widget_realize(window); 94 95 scrolledwin = gtk_scrolled_window_new(NULL, NULL); 96 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), 97 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 98 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin), 99 GTK_SHADOW_IN); 100 gtk_container_add(GTK_CONTAINER(window), scrolledwin); 101 gtk_widget_show(scrolledwin); 102 103 text = gtk_text_view_new(); 104 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); 105 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); 106 logwin->buffer = buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 107 108 g_object_ref(G_OBJECT(logwin->buffer)); 109 gtk_text_view_set_buffer(GTK_TEXT_VIEW(text), NULL); 110 logwin->hidden = TRUE; 111 logwin->never_shown = TRUE; 112 113 gtk_text_buffer_get_start_iter(buffer, &iter); 114 logwin->end_mark = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE); 115 116 g_signal_connect(G_OBJECT(text), "populate-popup", 117 G_CALLBACK(log_window_popup_menu_extend), logwin); 118 gtk_container_add(GTK_CONTAINER(scrolledwin), text); 119 gtk_widget_show(text); 120 121 g_signal_connect(G_OBJECT(window), "size_allocate", 122 G_CALLBACK(size_allocate_cb), GINT_TO_POINTER(instance)); 123 124 if (!geometry.min_height) { 125 geometry.min_width = 520; 126 geometry.min_height = 400; 127 } 128 129 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry, 130 GDK_HINT_MIN_SIZE); 131 gtk_window_set_default_size(GTK_WINDOW(window), *prefs_logwin_width, 132 *prefs_logwin_height); 133 134 logwin->window = window; 135 logwin->scrolledwin = scrolledwin; 136 logwin->text = text; 137 logwin->hook_id = hooks_register_hook(get_log_hook(instance), log_window_append, logwin); 138 logwin->has_error_capability = get_log_error_capability(instance); 139 140 return logwin; 141 } 142 143 void log_window_init(LogWindow *logwin) 144 { 145 GtkTextBuffer *buffer; 146 147 logwin->msg_color = &prefs_common.color[COL_LOG_MSG]; 148 logwin->warn_color = &prefs_common.color[COL_LOG_WARN]; 149 logwin->error_color = &prefs_common.color[COL_LOG_ERROR]; 150 logwin->in_color = &prefs_common.color[COL_LOG_IN]; 151 logwin->out_color = &prefs_common.color[COL_LOG_OUT]; 152 logwin->status_ok_color = "#00af00"; // green 153 logwin->status_nok_color = "#0000af"; // blue 154 logwin->status_skip_color = "#aa00aa"; // purple 155 156 buffer = logwin->buffer; 157 gtk_text_buffer_create_tag(buffer, "message", 158 "foreground-rgba", logwin->msg_color, 159 NULL); 160 gtk_text_buffer_create_tag(buffer, "warn", 161 "foreground-rgba", logwin->warn_color, 162 NULL); 163 logwin->error_tag = gtk_text_buffer_create_tag(buffer, "error", 164 "foreground-rgba", logwin->error_color, 165 NULL); 166 gtk_text_buffer_create_tag(buffer, "input", 167 "foreground-rgba", logwin->in_color, 168 NULL); 169 gtk_text_buffer_create_tag(buffer, "output", 170 "foreground-rgba", logwin->out_color, 171 NULL); 172 gtk_text_buffer_create_tag(buffer, "status_ok", 173 "foreground-rgba", logwin->status_ok_color, 174 NULL); 175 gtk_text_buffer_create_tag(buffer, "status_nok", 176 "foreground-rgba", logwin->status_nok_color, 177 NULL); 178 gtk_text_buffer_create_tag(buffer, "status_skip", 179 "foreground-rgba", logwin->status_skip_color, 180 NULL); 181 } 182 183 void log_window_show(LogWindow *logwin) 184 { 185 GtkTextView *text = GTK_TEXT_VIEW(logwin->text); 186 GtkTextBuffer *buffer = logwin->buffer; 187 GtkTextMark *mark; 188 189 logwin->hidden = FALSE; 190 191 if (logwin->never_shown) 192 gtk_text_view_set_buffer(GTK_TEXT_VIEW(logwin->text), logwin->buffer); 193 194 logwin->never_shown = FALSE; 195 196 mark = gtk_text_buffer_get_mark(buffer, "end"); 197 gtk_text_view_scroll_mark_onscreen(text, mark); 198 199 gtk_window_deiconify(GTK_WINDOW(logwin->window)); 200 gtk_widget_show(logwin->window); 201 gtk_window_present(GTK_WINDOW(logwin->window)); 202 } 203 204 static void log_window_jump_to_error(LogWindow *logwin) 205 { 206 GtkTextIter iter; 207 gtk_text_buffer_get_end_iter(logwin->buffer, &iter); 208 if (!gtk_text_iter_backward_to_tag_toggle(&iter, logwin->error_tag)) 209 return; 210 211 gtk_text_iter_backward_line(&iter); 212 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(logwin->text), &iter, 0, TRUE, 0, 0); 213 } 214 215 void log_window_show_error(LogWindow *logwin) 216 { 217 log_window_show(logwin); 218 log_window_jump_to_error(logwin); 219 } 220 221 static gboolean log_window_append(gpointer source, gpointer data) 222 { 223 LogText *logtext = (LogText *) source; 224 LogWindow *logwindow = (LogWindow *) data; 225 GtkTextView *text; 226 GtkTextBuffer *buffer; 227 GtkTextIter iter; 228 gchar *head = NULL; 229 const gchar *tag; 230 231 cm_return_val_if_fail(logtext != NULL, TRUE); 232 cm_return_val_if_fail(logtext->text != NULL, TRUE); 233 cm_return_val_if_fail(logwindow != NULL, FALSE); 234 235 text = GTK_TEXT_VIEW(logwindow->text); 236 buffer = logwindow->buffer; 237 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); 238 239 switch (logtext->type) { 240 case LOG_MSG: 241 tag = "message"; 242 head = "* "; 243 break; 244 case LOG_WARN: 245 tag = "warn"; 246 head = "** "; 247 break; 248 case LOG_ERROR: 249 tag = "error"; 250 head = "*** "; 251 logwindow->has_error = TRUE; 252 break; 253 case LOG_STATUS_OK: 254 tag = "status_ok"; 255 head = "> "; 256 break; 257 case LOG_STATUS_NOK: 258 tag = "status_nok"; 259 head = "> "; 260 break; 261 case LOG_STATUS_SKIP: 262 tag = "status_skip"; 263 head = "> skipped: "; 264 break; 265 default: 266 tag = NULL; 267 break; 268 } 269 270 if (logtext->instance == LOG_PROTOCOL) { 271 if (tag == NULL) { 272 if (strstr(logtext->text, "] POP>") 273 || strstr(logtext->text, "] IMAP>") 274 || strstr(logtext->text, "] SMTP>") 275 || strstr(logtext->text, "] ESMTP>") 276 || strstr(logtext->text, "] NNTP>")) 277 tag = "output"; 278 if (strstr(logtext->text, "] POP<") 279 || strstr(logtext->text, "] IMAP<") 280 || strstr(logtext->text, "] SMTP<") 281 || strstr(logtext->text, "] ESMTP<") 282 || strstr(logtext->text, "] NNTP<")) 283 tag = "input"; 284 } 285 } 286 287 if (head) 288 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, head, -1, 289 tag, NULL); 290 291 if (!g_utf8_validate(logtext->text, -1, NULL)) { 292 gchar * mybuf = g_malloc(strlen(logtext->text)*2 +1); 293 conv_localetodisp(mybuf, strlen(logtext->text)*2 +1, logtext->text); 294 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, mybuf, -1, 295 tag, NULL); 296 g_free(mybuf); 297 } else { 298 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, logtext->text, -1, 299 tag, NULL); 300 } 301 gtk_text_buffer_get_start_iter(buffer, &iter); 302 303 if (!logwindow->hidden) { 304 GtkAdjustment *vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(text)); 305 gfloat upper = gtk_adjustment_get_upper(vadj) - 306 gtk_adjustment_get_page_size(vadj); 307 gfloat value = gtk_adjustment_get_value(vadj); 308 if (value == upper || 309 (upper - value < 16 && value < 8)) 310 gtk_text_view_scroll_mark_onscreen(text, logwindow->end_mark); 311 } 312 313 return FALSE; 314 } 315 316 static void hide_cb(GtkWidget *widget, LogWindow *logwin) 317 { 318 logwin->hidden = TRUE; 319 } 320 321 static void log_window_clear(GtkWidget *widget, LogWindow *logwin) 322 { 323 GtkTextBuffer *textbuf = logwin->buffer; 324 GtkTextIter start_iter, end_iter; 325 326 gtk_text_buffer_get_start_iter(textbuf, &start_iter); 327 gtk_text_buffer_get_end_iter(textbuf, &end_iter); 328 gtk_text_buffer_delete(textbuf, &start_iter, &end_iter); 329 } 330 331 static void log_window_go_to_last_error(GtkWidget *widget, LogWindow *logwin) 332 { 333 log_window_jump_to_error(logwin); 334 } 335 336 static void log_window_popup_menu_extend(GtkTextView *textview, 337 GtkMenu *menu, LogWindow *logwin) 338 { 339 GtkWidget *menuitem; 340 341 cm_return_if_fail(menu != NULL); 342 cm_return_if_fail(GTK_IS_MENU_SHELL(menu)); 343 344 menuitem = gtk_separator_menu_item_new(); 345 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem); 346 gtk_widget_show(menuitem); 347 348 if (logwin->has_error_capability) { 349 menuitem = gtk_menu_item_new_with_mnemonic(_("_Go to last error")); 350 g_signal_connect(G_OBJECT(menuitem), "activate", 351 G_CALLBACK(log_window_go_to_last_error), logwin); 352 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem); 353 gtk_widget_show(menuitem); 354 } 355 356 menuitem = gtk_menu_item_new_with_mnemonic(_("Clear _Log")); 357 g_signal_connect(G_OBJECT(menuitem), "activate", 358 G_CALLBACK(log_window_clear), logwin); 359 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem); 360 gtk_widget_show(menuitem); 361 } 362