Fix a segfault occuring when logging an empty message and there is no log window.
[geeqie.git] / src / logwindow.c
1 /*
2  * Geeqie
3  * Copyright (C) 2008 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik, Laurent Monin
6  * based on logwindow.[ch] from Sylpheed 2.4.7 (C) Hiroyuki Yamamoto
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #include "main.h"
14 #include "logwindow.h"
15
16 #include "window.h"
17
18 #include <gdk/gdkkeysyms.h>
19
20
21 typedef struct _LogWindow LogWindow;
22
23 struct _LogWindow
24 {
25         GtkWidget *window;
26         GtkWidget *scrolledwin;
27         GtkWidget *text;
28         
29         GdkColor colors[LOG_COUNT];
30
31         gint lines;
32 };
33
34 typedef struct _LogDef LogDef;
35 struct _LogDef
36 {
37         LogType type;
38         const gchar *tag;
39         const gchar *color;
40 };
41
42 /* Keep LogType order !! */
43 static LogDef logdefs[LOG_COUNT] = {
44         { LOG_NORMAL,   "normal",       "black"  },
45         { LOG_MSG,      "message",      "blue"   },
46         { LOG_WARN,     "warning",      "orange" },
47         { LOG_ERROR,    "error",        "red"    },
48 };
49
50 static LogWindow *logwindow = NULL;
51
52 static void hide_cb(GtkWidget *widget, LogWindow *logwin)
53 {
54 }
55
56 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
57                             LogWindow *logwin)
58 {
59         if (event && event->keyval == GDK_Escape)
60                 gtk_widget_hide(logwin->window);
61         return FALSE;
62 }
63
64 static LogWindow *log_window_create(void)
65 {
66         LogWindow *logwin;
67         GtkWidget *window;
68         GtkWidget *scrolledwin;
69         GtkWidget *text;
70         GtkTextBuffer *buffer;
71         GtkTextIter iter;
72
73         logwin = g_new0(LogWindow, 1);
74
75         window = window_new(GTK_WINDOW_TOPLEVEL, "log", NULL, NULL, _("Log"));
76         gtk_widget_set_size_request(window, 520, 400);
77         g_signal_connect(G_OBJECT(window), "delete_event",
78                          G_CALLBACK(gtk_widget_hide_on_delete), NULL);
79         g_signal_connect(G_OBJECT(window), "key_press_event",
80                          G_CALLBACK(key_pressed), logwin);
81         g_signal_connect(G_OBJECT(window), "hide",
82                          G_CALLBACK(hide_cb), logwin);
83         gtk_widget_realize(window);
84
85         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
86         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
87                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
88         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
89                                             GTK_SHADOW_IN);
90         gtk_container_add(GTK_CONTAINER(window), scrolledwin);
91         gtk_widget_show(scrolledwin);
92
93         text = gtk_text_view_new();
94         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
95         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
96         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
97         gtk_text_buffer_get_start_iter(buffer, &iter);
98         gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);
99         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
100         gtk_widget_show(text);
101
102         logwin->window = window;
103         logwin->scrolledwin = scrolledwin;
104         logwin->text = text;
105         logwin->lines = 1;
106
107         return logwin;
108 }
109
110 static void log_window_init(LogWindow *logwin)
111 {
112         GtkTextBuffer *buffer;
113         GdkColormap *colormap;
114         gboolean success[LOG_COUNT - 1];
115         gint i;
116
117         g_assert(logwin != NULL);
118         g_assert(logwin->colors != NULL);
119
120         for (i = LOG_NORMAL; i < LOG_COUNT; i++)
121                 {
122                 gboolean ok = gdk_color_parse(logdefs[i].color, &logwin->colors[i]);
123                 if (ok == TRUE) continue;
124
125                 if (i == LOG_NORMAL) return;
126                 memcpy(&logwin->colors[i], &logwin->colors[LOG_NORMAL], sizeof(GdkColor));
127                 }
128
129         colormap = gdk_drawable_get_colormap(logwin->window->window);
130         gdk_colormap_alloc_colors(colormap, logwin->colors, LOG_COUNT, FALSE, TRUE, success);
131
132         for (i = LOG_NORMAL; i < LOG_COUNT; i++)
133                 {
134                 if (success[i] == FALSE)
135                         {
136                         GtkStyle *style;
137                         gint j;
138
139                         g_warning("LogWindow: color allocation failed\n");
140                         style = gtk_widget_get_style(logwin->window);
141                         for (j = LOG_NORMAL; j < LOG_COUNT; j++)
142                                 logwin->colors[j] = style->black;
143                         break;
144                         }
145                 }
146
147         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(logwin->text));
148         for (i = LOG_NORMAL; i < LOG_COUNT; i++)
149                 gtk_text_buffer_create_tag(buffer, logdefs[i].tag,
150                                            "foreground-gdk", &logwin->colors[i],
151                                            "family", "MonoSpace",
152                                            NULL);
153 }
154
155 static void log_window_show(LogWindow *logwin)
156 {
157         GtkTextView *text = GTK_TEXT_VIEW(logwin->text);
158         GtkTextBuffer *buffer;
159         GtkTextMark *mark;
160         
161         g_assert(logwin != NULL);
162
163         buffer = gtk_text_view_get_buffer(text);
164         mark = gtk_text_buffer_get_mark(buffer, "end");
165         gtk_text_view_scroll_mark_onscreen(text, mark);
166
167         gtk_window_present(GTK_WINDOW(logwin->window));
168
169         log_window_append("", LOG_NORMAL); // to flush memorized lines
170 }
171
172 void log_window_new(void)
173 {
174         if (logwindow == NULL)
175                 {
176                 LogWindow *logwin;
177
178                 logwin = log_window_create();
179                 log_window_init(logwin);
180                 logwindow = logwin;
181                 }
182
183         log_window_show(logwindow);
184 }
185
186 typedef struct _LogMsg LogMsg;
187
188 struct _LogMsg {
189         gchar *text;
190         LogType type;
191 };
192
193
194 static void log_window_insert_text(GtkTextBuffer *buffer, GtkTextIter *iter,
195                                    const gchar *text, const gchar *tag)
196 {
197         gchar *str_utf8;
198
199         if (!text || !*text) return;
200
201         str_utf8 = utf8_validate_or_convert((gchar *)text);
202         gtk_text_buffer_insert_with_tags_by_name(buffer, iter, str_utf8, -1, tag, NULL);
203 }
204
205
206 void log_window_append(const gchar *str, LogType type)
207 {
208         GtkTextView *text;
209         GtkTextBuffer *buffer;
210         GtkTextIter iter;
211         gint line_limit = 1000; //FIXME: option
212         static GList *memory = NULL;
213
214         if (logwindow == NULL)
215                 {
216                 if (*str) {
217                         LogMsg *msg = g_new(LogMsg, 1);
218
219                         msg->text = g_strdup(str);
220                         msg->type = type;
221
222                         memory = g_list_prepend(memory, msg);
223
224                         while (g_list_length(memory) >= line_limit)
225                                 {
226                                 GList *work = g_list_last(memory);
227                                 LogMsg *oldest_msg = work->data;
228                         
229                                 g_free(oldest_msg->text);
230                                 memory = g_list_delete_link(memory, work);
231                                 }
232                         }
233                 return;
234                 }
235
236         text = GTK_TEXT_VIEW(logwindow->text);
237         buffer = gtk_text_view_get_buffer(text);
238
239         if (line_limit > 0 && logwindow->lines >= line_limit)
240                 {
241                 GtkTextIter start, end;
242
243                 gtk_text_buffer_get_start_iter(buffer, &start);
244                 end = start;
245                 gtk_text_iter_forward_lines(&end, logwindow->lines - line_limit);
246                 gtk_text_buffer_delete(buffer, &start, &end);
247                 }
248
249         gtk_text_buffer_get_end_iter(buffer, &iter);
250
251         {
252         GList *work = g_list_last(memory);
253
254         while (work)
255                 {
256                 GList *prev;
257                 LogMsg *oldest_msg = work->data;
258                 
259                 log_window_insert_text(buffer, &iter, oldest_msg->text, logdefs[oldest_msg->type].tag);
260                 
261                 prev = work->prev;
262                 memory = g_list_delete_link(memory, work);
263                 work = prev;
264                 }
265         }
266
267         log_window_insert_text(buffer, &iter, str, logdefs[type].tag);
268
269         if (GTK_WIDGET_VISIBLE(text))
270                 {
271                 GtkTextMark *mark;
272                 
273                 mark = gtk_text_buffer_get_mark(buffer, "end");
274                 gtk_text_view_scroll_mark_onscreen(text, mark);
275                 }
276
277         logwindow->lines = gtk_text_buffer_get_line_count(buffer);
278 }