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