1 /* EasyTAG - Tag editor for audio files
2 * Copyright (C) 2014-2015 David King <amigadave@amigadave.com>
3 * Copyright (C) 2000-2007 Jerome Couderc <easytag@gmail.com>
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 2 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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include <glib/gi18n.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25
26 #include "log.h"
27 #include "application_window.h"
28 #include "easytag.h"
29 #include "setting.h"
30 #include "charset.h"
31
32 #include "win32/win32dep.h"
33
34 typedef struct
35 {
36 GtkWidget *log_view;
37 GtkListStore *log_model;
38
39 /* Popup menu. */
40 GtkWidget *menu;
41 } EtLogAreaPrivate;
42
43 G_DEFINE_TYPE_WITH_PRIVATE (EtLogArea, et_log_area, GTK_TYPE_BIN)
44
45 enum
46 {
47 LOG_ICON_NAME,
48 LOG_TIME_TEXT,
49 LOG_TEXT,
50 LOG_COLUMN_COUNT
51 };
52
53 /* File for log. */
54 static const gchar LOG_FILE[] = "easytag.log";
55
56 /**************
57 * Prototypes *
58 **************/
59 static void Log_List_Set_Row_Visible (EtLogArea *self, GtkTreeIter *rowIter);
60 static gchar *Log_Format_Date (void);
61
62
63
64 /*************
65 * Functions *
66 *************/
67
68 static void
do_popup_menu(EtLogArea * self,GdkEventButton * event)69 do_popup_menu (EtLogArea *self,
70 GdkEventButton *event)
71 {
72 EtLogAreaPrivate *priv;
73 gint button;
74 gint event_time;
75
76 priv = et_log_area_get_instance_private (self);
77
78 if (event)
79 {
80 button = event->button;
81 event_time = event->time;
82 }
83 else
84 {
85 button = 0;
86 event_time = gtk_get_current_event_time ();
87 }
88
89 /* TODO: Add popup positioning function. */
90 gtk_menu_popup (GTK_MENU (priv->menu), NULL, NULL, NULL, NULL, button,
91 event_time);
92 }
93
94 static gboolean
on_popup_menu(GtkWidget * treeview,EtLogArea * self)95 on_popup_menu (GtkWidget *treeview,
96 EtLogArea *self)
97 {
98 do_popup_menu (self, NULL);
99
100 return GDK_EVENT_STOP;
101 }
102
103 /*
104 * Log_Popup_Menu_Handler : displays the corresponding menu
105 */
106 static gboolean
on_button_press_event(GtkWidget * treeview,GdkEventButton * event,EtLogArea * self)107 on_button_press_event (GtkWidget *treeview,
108 GdkEventButton *event,
109 EtLogArea *self)
110 {
111 if (gdk_event_triggers_context_menu ((GdkEvent *)event))
112 {
113 do_popup_menu (self, event);
114
115 return GDK_EVENT_STOP;
116 }
117
118 return GDK_EVENT_PROPAGATE;
119 }
120
121
122 static void
et_log_area_class_init(EtLogAreaClass * klass)123 et_log_area_class_init (EtLogAreaClass *klass)
124 {
125 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
126
127 gtk_widget_class_set_template_from_resource (widget_class,
128 "/org/gnome/EasyTAG/log_area.ui");
129 gtk_widget_class_bind_template_child_private (widget_class, EtLogArea,
130 log_view);
131 gtk_widget_class_bind_template_child_private (widget_class, EtLogArea,
132 log_model);
133 }
134
135 static void
et_log_area_init(EtLogArea * self)136 et_log_area_init (EtLogArea *self)
137 {
138 EtLogAreaPrivate *priv;
139 GtkBuilder *builder;
140 GMenuModel *menu_model;
141
142 priv = et_log_area_get_instance_private (self);
143
144 gtk_widget_init_template (GTK_WIDGET (self));
145
146 /* Create popup menu. */
147 builder = gtk_builder_new_from_resource ("/org/gnome/EasyTAG/menus.ui");
148
149 menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "log-menu"));
150 priv->menu = gtk_menu_new_from_model (menu_model);
151 gtk_menu_attach_to_widget (GTK_MENU (priv->menu), priv->log_view, NULL);
152
153 g_object_unref (builder);
154
155 g_signal_connect (priv->log_view, "popup-menu", G_CALLBACK (on_popup_menu),
156 self);
157 g_signal_connect (priv->log_view, "button-press-event",
158 G_CALLBACK (on_button_press_event), self);
159 }
160
161
162 GtkWidget *
et_log_area_new(void)163 et_log_area_new (void)
164 {
165 return g_object_new (ET_TYPE_LOG_AREA, NULL);
166 }
167
168 /*
169 * Set a row visible in the log list (by scrolling the list)
170 */
171 static void
Log_List_Set_Row_Visible(EtLogArea * self,GtkTreeIter * rowIter)172 Log_List_Set_Row_Visible (EtLogArea *self,
173 GtkTreeIter *rowIter)
174 {
175 EtLogAreaPrivate *priv;
176 GtkTreePath *start;
177 GtkTreePath *end;
178
179 priv = et_log_area_get_instance_private (self);
180
181 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (priv->log_view),
182 &start, &end))
183 {
184 GtkTreePath *rowPath;
185
186 rowPath = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->log_model),
187 rowIter);
188
189 if ((gtk_tree_path_compare (rowPath, start) < 0)
190 || (gtk_tree_path_compare (rowPath, end) > 0))
191 {
192 /* rowPath is not is the visible range. */
193 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->log_view),
194 rowPath, NULL, FALSE, 0, 0);
195 }
196
197 gtk_tree_path_free (start);
198 gtk_tree_path_free (end);
199 gtk_tree_path_free (rowPath);
200 }
201 }
202
203
204 /*
205 * Remove all lines in the log list
206 */
207 void
et_log_area_clear(EtLogArea * self)208 et_log_area_clear (EtLogArea *self)
209 {
210 EtLogAreaPrivate *priv;
211
212 g_return_if_fail (ET_LOG_AREA (self));
213
214 priv = et_log_area_get_instance_private (self);
215
216 if (priv->log_model)
217 {
218 gtk_list_store_clear (priv->log_model);
219 }
220 }
221
222
223 /*
224 * Return time in allocated data
225 */
226 static gchar *
Log_Format_Date(void)227 Log_Format_Date (void)
228 {
229 GDateTime *dt;
230 gchar *time;
231
232 dt = g_date_time_new_now_local ();
233 /* Time without date in current locale. */
234 time = g_date_time_format (dt, "%X");
235
236 g_date_time_unref (dt);
237
238 return time;
239 }
240
241 static const gchar *
get_icon_name_from_error_kind(EtLogAreaKind error_kind)242 get_icon_name_from_error_kind (EtLogAreaKind error_kind)
243 {
244 switch (error_kind)
245 {
246 /* Same icon for information and OK messages. */
247 case LOG_OK:
248 case LOG_INFO:
249 return "dialog-information";
250 break;
251 case LOG_WARNING:
252 return "dialog-warning";
253 break;
254 case LOG_ERROR:
255 return "dialog-error";
256 break;
257 case LOG_UNKNOWN:
258 return NULL;
259 break;
260 default:
261 g_assert_not_reached ();
262 }
263 }
264
265 /*
266 * Function to use anywhere in the application to send a message to the LogList
267 */
268 void
Log_Print(EtLogAreaKind error_type,const gchar * const format,...)269 Log_Print (EtLogAreaKind error_type, const gchar * const format, ...)
270 {
271 EtLogArea *self;
272 EtLogAreaPrivate *priv;
273 va_list args;
274 gchar *string;
275 gchar *time;
276 GtkTreeIter iter;
277 static gboolean first_time = TRUE;
278 static gchar *file_path = NULL;
279 GFile *file;
280 GFileOutputStream *file_ostream;
281 GError *error = NULL;
282
283 self = ET_LOG_AREA (et_application_window_get_log_area (ET_APPLICATION_WINDOW (MainWindow)));
284
285 g_return_if_fail (self != NULL);
286
287 priv = et_log_area_get_instance_private (self);
288
289 va_start (args, format);
290 string = g_strdup_vprintf (format, args);
291 va_end (args);
292
293 time = Log_Format_Date ();
294
295 gtk_list_store_insert_with_values (priv->log_model, &iter, G_MAXINT,
296 LOG_ICON_NAME,
297 get_icon_name_from_error_kind (error_type),
298 LOG_TIME_TEXT, time, LOG_TEXT,
299 string, -1);
300 Log_List_Set_Row_Visible (self, &iter);
301 g_free (time);
302
303 // Store also the messages in the log file.
304 if (!file_path)
305 {
306 gchar *cache_path = g_build_filename (g_get_user_cache_dir (),
307 PACKAGE_TARNAME, NULL);
308
309 if (!g_file_test (cache_path, G_FILE_TEST_IS_DIR))
310 {
311 gint result = g_mkdir_with_parents (cache_path, S_IRWXU);
312
313 if (result == -1)
314 {
315 g_printerr ("%s", "Unable to create cache directory");
316 g_free (cache_path);
317 g_free (string);
318
319 return;
320 }
321 }
322
323 file_path = g_build_filename (cache_path, LOG_FILE, NULL);
324 g_free (cache_path);
325 }
326
327 file = g_file_new_for_path (file_path);
328
329 /* On startup, the log is cleared. The log is then appended to for the
330 * remainder of the application lifetime. */
331 if (first_time)
332 {
333 file_ostream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
334 NULL, &error);
335 }
336 else
337 {
338 file_ostream = g_file_append_to (file, G_FILE_CREATE_NONE, NULL,
339 &error);
340 }
341
342 if (file_ostream)
343 {
344 GString *data;
345 gsize bytes_written;
346
347 time = Log_Format_Date ();
348 data = g_string_new (time);
349 g_free (time);
350
351 data = g_string_append_c (data, ' ');
352 data = g_string_append (data, string);
353 g_free (string);
354
355 data = g_string_append_c (data, '\n');
356
357 if (!g_output_stream_write_all (G_OUTPUT_STREAM (file_ostream),
358 data->str, data->len, &bytes_written,
359 NULL, &error))
360 {
361 g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
362 "bytes of data were written", bytes_written, data->len);
363
364 /* To avoid recursion of Log_Print. */
365 g_warning ("Error writing to the log file '%s' ('%s')", file_path,
366 error->message);
367
368 g_error_free (error);
369
370 g_string_free (data, TRUE);
371 g_object_unref (file_ostream);
372 g_object_unref (file);
373
374 return;
375 }
376
377 first_time = FALSE;
378
379 g_string_free (data, TRUE);
380 }
381 else
382 {
383 g_warning ("Error opening output stream of file '%s' ('%s')",
384 file_path, error->message);
385 g_error_free (error);
386 }
387
388 g_object_unref (file_ostream);
389 g_object_unref (file);
390 }
391