1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2011 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <gtk/gtk.h>
21 #include <time.h>
22 #include "main.h"
23 #include "playlist3.h"
24
25 struct _Playlist3MessagePlugin
26 {
27 GmpcPluginBase parent_instance;
28 Playlist3MessagePluginPrivate *priv;
29 };
30
31 struct _Playlist3MessagePluginClass
32 {
33 GmpcPluginBaseClass parent_class;
34 };
35 static const char *error_levels[4] = {
36 N_("Info"),
37 N_("Warning"),
38 N_("Critical"),
39 N_("User question")
40 };
41
42 /**
43 * Private data structure
44 */
45 typedef struct _Playlist3MessagePluginPrivate
46 {
47 gboolean error_visible;
48 ErrorLevel last_error_level;
49 guint timeout_callback;
50 GtkListStore *message_list;
51 GIOChannel *log_file;
52 } _Playlist3MessagePluginPrivate;
53
54 void copy_to_clipboard(GtkButton * button, GtkBuilder * xml);
55
playlist3_message_destroy(Playlist3MessagePlugin * self)56 static void playlist3_message_destroy(Playlist3MessagePlugin * self)
57 {
58 g_io_channel_flush(self->priv->log_file, NULL);
59 g_io_channel_unref(self->priv->log_file);
60 }
61
playlist3_message_init(Playlist3MessagePlugin * self)62 static void playlist3_message_init(Playlist3MessagePlugin * self)
63 {
64 if (!self->priv->message_list)
65 {
66 GError *error = NULL;
67 gchar *path = gmpc_get_user_path("gmpc.log");
68 self->priv->message_list = gtk_list_store_new(3, G_TYPE_INT64, G_TYPE_STRING, G_TYPE_STRING);
69
70 self->priv->log_file = g_io_channel_new_file(path, "a", &error);
71 if (error)
72 {
73 g_error("Failed to log file: '%s'", error->message);
74 }
75 q_free(path);
76
77 g_io_channel_flush(self->priv->log_file, NULL);
78 }
79 }
80
playlist3_message_show(Playlist3MessagePlugin * self,const gchar * message,ErrorLevel el)81 void playlist3_message_show(Playlist3MessagePlugin * self, const gchar * message, ErrorLevel el)
82 {
83 GtkMessageType m;
84 gchar text[64];
85 struct tm *lt;
86 GtkTreeIter iter;
87 time_t t = time(NULL);
88 ErrorLevel level;
89 const gchar *image_name;
90 gchar *string;
91 playlist3_message_init(self);
92 gtk_list_store_prepend(self->priv->message_list, &iter);
93 gtk_list_store_set(self->priv->message_list, &iter, 0, (gint64) t, 2, message, -1);
94
95 lt = localtime(&t);
96 strftime(text, 64, "%d/%m/%Y-%H:%M:%S", lt);
97
98 string = g_strdup_printf("%s:%s:%s\n", text, error_levels[el], message);
99 g_io_channel_write_chars(self->priv->log_file, string, -1, NULL, NULL);
100 q_free(string);
101 g_io_channel_flush(self->priv->log_file, NULL);
102
103 level = cfg_get_single_value_as_int_with_default(config, "Default", "min-error-level", ERROR_WARNING);
104 switch (el)
105 {
106 case ERROR_CRITICAL:
107 image_name = GTK_STOCK_DIALOG_ERROR;
108 m = GTK_MESSAGE_ERROR;
109 break;
110 case ERROR_WARNING:
111 image_name = GTK_STOCK_DIALOG_WARNING;
112 m = GTK_MESSAGE_WARNING;
113 break;
114 case USER_FEEDBACK:
115 m = GTK_MESSAGE_QUESTION;
116 image_name = GTK_STOCK_DIALOG_QUESTION;
117 break;
118 case ERROR_INFO:
119 default:
120 image_name = GTK_STOCK_DIALOG_INFO;
121 m = GTK_MESSAGE_INFO;
122 break;
123 }
124 gtk_list_store_set(self->priv->message_list, &iter, 1, image_name, -1);
125 if (el < level)
126 {
127 return;
128 }
129 if (self->priv->error_visible)
130 {
131 /* higher level errors are not overwritten by lower level errors */
132 if (el < self->priv->last_error_level)
133 {
134 return;
135 }
136 playlist3_close_error();
137 if (self->priv->timeout_callback)
138 {
139 g_source_remove(self->priv->timeout_callback);
140 }
141 self->priv->timeout_callback = 0;
142
143 }
144 /* store last level */
145 self->priv->last_error_level = el;
146 if (pl3_xml && pl3_zoom != PLAYLIST_MINI)
147 {
148 GtkWidget *event;
149 GtkWidget *label = NULL;
150 GtkWidget *box = gtk_info_bar_new();
151 GtkWidget *ca = gtk_info_bar_get_content_area(GTK_INFO_BAR(box));
152 GtkWidget *hbox = gtk_hbox_new(FALSE, 6);
153 GtkWidget *image = gtk_image_new_from_stock(image_name, GTK_ICON_SIZE_BUTTON);
154
155 /* Get placeholder */
156 event = GTK_WIDGET(gtk_builder_get_object(pl3_xml, "error_event"));
157 /* Add infobar to place holder */
158 gtk_container_add(GTK_CONTAINER(event), box);
159
160 /* Add close button */
161 if(el < USER_FEEDBACK) {
162 gtk_info_bar_add_button(GTK_INFO_BAR(box), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
163 }else{
164 gtk_info_bar_add_button(GTK_INFO_BAR(box), GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE);
165 }
166 /* Set message type, so bg color changes accordingly */
167 gtk_info_bar_set_message_type(GTK_INFO_BAR(box), m);
168
169 /* Add image to box */
170 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
171 /* Add label */
172 label = gtk_label_new("");
173 gtk_label_set_markup(GTK_LABEL(label), message);
174 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
175 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
176 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
177
178 /* Add image + label to info bar */
179 gtk_container_add(GTK_CONTAINER(ca), hbox);
180 /* show it */
181 gtk_widget_show_all(event);
182 /* Watch signals, like user input */
183 g_signal_connect_swapped(G_OBJECT(box), "response", G_CALLBACK(playlist3_message_close), self);
184
185 /* Error */
186 self->priv->error_visible = TRUE;
187 /* Close it after 5 seconds */
188 if(el < USER_FEEDBACK)
189 self->priv->timeout_callback = g_timeout_add_seconds(5, (GSourceFunc) playlist3_message_close, self);
190 else
191 self->priv->timeout_callback = g_timeout_add_seconds(30, (GSourceFunc) playlist3_message_close, self);
192 } else
193 {
194 self->priv->error_visible = FALSE;
195 }
196 }
197
198 /* Indicates if a widget is allready added */
199 static gboolean widget_added = FALSE;
200
playlist3_message_add_widget(Playlist3MessagePlugin * self,GtkWidget * widget)201 void playlist3_message_add_widget(Playlist3MessagePlugin * self, GtkWidget * widget)
202 {
203 GtkWidget *event = GTK_WIDGET(gtk_builder_get_object(pl3_xml, "error_event"));
204 GtkWidget *box = gtk_bin_get_child(GTK_BIN(event));
205 if(box == NULL)
206 return;
207 /* Avoid adding more then one widget */
208 if (widget_added)
209 return;
210 widget_added = TRUE;
211
212 gtk_info_bar_add_action_widget(GTK_INFO_BAR(box), widget, GTK_RESPONSE_CLOSE);
213 gtk_widget_show_all(event);
214 }
215
playlist3_message_close(Playlist3MessagePlugin * self)216 gboolean playlist3_message_close(Playlist3MessagePlugin * self)
217 {
218 /* reset */
219 widget_added = FALSE;
220 if (self->priv->error_visible)
221 {
222 self->priv->error_visible = FALSE;
223 g_source_remove(self->priv->timeout_callback);
224
225 if (pl3_xml)
226 {
227 GtkWidget *event = GTK_WIDGET(gtk_builder_get_object(pl3_xml, "error_event"));
228 gtk_widget_hide(event);
229 gtk_container_foreach(GTK_CONTAINER(event), (GtkCallback) (gtk_widget_destroy), NULL);
230 }
231 }
232 self->priv->timeout_callback = 0;
233 return FALSE;
234 }
235
message_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)236 static void message_cell_data_func(GtkTreeViewColumn * tree_column,
237 GtkCellRenderer * cell, GtkTreeModel * tree_model, GtkTreeIter * iter, gpointer data)
238 {
239 time_t t;
240 gint64 id;
241 gchar text[64];
242 struct tm *lt;
243 gtk_tree_model_get(tree_model, iter, 0, &id, -1);
244 /* gtk_list_store only knows the type int64, not time_T
245 * so lets do some casting)
246 */
247 t = (time_t) id;
248 lt = localtime(&t);
249 strftime(text, 64, "%H:%M:%S", lt);
250 g_object_set(G_OBJECT(cell), "text", text, NULL);
251 }
252
253 /**
254 * The list of messages
255 */
256
257 void message_window_destroy(GtkWidget * win, GdkEvent * event, GtkBuilder * message_xml);
message_window_destroy(GtkWidget * win,GdkEvent * event,GtkBuilder * message_xml)258 void message_window_destroy(GtkWidget * win, GdkEvent * event, GtkBuilder * message_xml)
259 {
260 gtk_widget_destroy(win);
261 g_object_unref(message_xml);
262 message_xml = NULL;
263 }
264
copy_to_clipboard(GtkButton * button,GtkBuilder * xml)265 void copy_to_clipboard(GtkButton * button, GtkBuilder * xml)
266 {
267 GtkWidget *tree = GTK_WIDGET(gtk_builder_get_object(xml, "message_tree"));
268 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
269 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
270 GtkClipboard *gcb = NULL;
271 GList *list = NULL, *liter = NULL;
272 GString *str = g_string_new("");
273 printf("Copy to clipboard\n");
274 if (gtk_tree_selection_count_selected_rows(selection) == 0)
275 {
276 GtkTreeIter iter;
277 if (gtk_tree_model_get_iter_first(model, &iter))
278 {
279 do
280 {
281 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
282 list = g_list_prepend(list, path);
283 } while (gtk_tree_model_iter_next(model, &iter));
284 }
285 list = g_list_reverse(list);
286 } else
287 {
288 list = gtk_tree_selection_get_selected_rows(selection, NULL);
289 }
290 for (liter = g_list_first(list); liter; liter = g_list_next(liter))
291 {
292 gchar *message = NULL;
293 GtkTreeIter iter;
294 if (gtk_tree_model_get_iter(model, &iter, liter->data))
295 {
296 gtk_tree_model_get(model, &iter, 2, &message, -1);
297 str = g_string_append(str, message);
298 str = g_string_append(str, "\n");
299 g_free(message);
300 }
301 }
302
303 gcb = gtk_widget_get_clipboard(GTK_WIDGET(button), GDK_SELECTION_CLIPBOARD);
304 printf("Set clipboard: %s\n", str->str);
305 gtk_clipboard_set_text(gcb, str->str, str->len);
306
307 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
308 g_list_free(list);
309 g_string_free(str, TRUE);
310 }
311
playlist3_message_window_open(Playlist3MessagePlugin * self)312 static void playlist3_message_window_open(Playlist3MessagePlugin * self)
313 {
314 GtkBuilder *message_xml = NULL;
315 GtkWidget *win, *pl3_win = playlist3_get_window();
316 GtkBuilder *xml;
317 GtkCellRenderer *renderer;
318 GtkWidget *tree;
319 gchar *path;
320 path = gmpc_get_full_glade_path("playlist-message-window.ui");
321 message_xml = xml = gtk_builder_new();
322 gtk_builder_add_from_file(xml, path, NULL);
323 q_free(path);
324 playlist3_message_init(self);
325
326 /* set transient */
327 win = GTK_WIDGET(gtk_builder_get_object(xml, "message_window"));
328 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(pl3_win));
329 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
330
331 tree = GTK_WIDGET(gtk_builder_get_object(xml, "message_tree"));
332 renderer = gtk_cell_renderer_pixbuf_new();
333 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree), -1, "", renderer, "stock-id", 1, NULL);
334 renderer = gtk_cell_renderer_text_new();
335 gtk_tree_view_insert_column_with_data_func(GTK_TREE_VIEW(tree), -1, _("Time"), renderer,
336 (GtkTreeCellDataFunc) message_cell_data_func, NULL, NULL);
337 renderer = gtk_cell_renderer_text_new();
338 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree), -1, _("Message"), renderer, "markup", 2, NULL);
339 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(self->priv->message_list));
340
341 gtk_widget_show(win);
342
343 gtk_builder_connect_signals(xml, xml);
344 }
345
346 /**
347 * Turn this into a plugin. This is a test implementation.
348 * This is not usefull-as-is
349 */
350 static void playlist3_message_plugin_class_init(Playlist3MessagePluginClass * klass);
351 GType playlist3_message_plugin_get_type(void);
352
playlist3_message_plugin_get_version(GmpcPluginBase * plug,int * length)353 static int *playlist3_message_plugin_get_version(GmpcPluginBase * plug, int *length)
354 {
355 static int version[3] = { 0, 0, 1 };
356 if (length)
357 *length = 3;
358 return (int *)version;
359 }
360
playlist3_message_plugin_get_name(GmpcPluginBase * plug)361 static const char *playlist3_message_plugin_get_name(GmpcPluginBase * plug)
362 {
363 return "Playlist3 Messages";
364 }
365
playlist3_message_plugin_finalize(GObject * obj)366 static void playlist3_message_plugin_finalize(GObject * obj)
367 {
368 Playlist3MessagePluginClass *klass = (g_type_class_peek(playlist3_message_plugin_get_type()));
369 gpointer parent_class = g_type_class_peek_parent(klass);
370 playlist3_message_destroy((Playlist3MessagePlugin *) obj);
371
372 if (((Playlist3MessagePlugin *) obj)->priv)
373 {
374 Playlist3MessagePluginPrivate *priv = ((Playlist3MessagePlugin *) obj)->priv;
375 if (priv->message_list)
376 g_object_unref(priv->message_list);
377 g_free(priv);
378 ((Playlist3MessagePlugin *) obj)->priv = NULL;
379 }
380 if (parent_class)
381 G_OBJECT_CLASS(parent_class)->finalize(obj);
382 }
383
playlist3_message_plugin_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)384 static GObject *playlist3_message_plugin_constructor(GType type, guint n_construct_properties,
385 GObjectConstructParam * construct_properties)
386 {
387 Playlist3MessagePluginClass *klass;
388 Playlist3MessagePlugin *self;
389 GObjectClass *parent_class;
390 klass = (g_type_class_peek(playlist3_message_plugin_get_type()));
391 parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(klass));
392 self = (Playlist3MessagePlugin *) parent_class->constructor(type, n_construct_properties, construct_properties);
393
394 /* setup private structure */
395 self->priv = g_malloc0(sizeof(Playlist3MessagePluginPrivate));
396 self->priv->error_visible = FALSE;
397 self->priv->last_error_level = ERROR_INFO;
398 self->priv->timeout_callback = 0;
399 self->priv->message_list = NULL;
400 self->priv->log_file = NULL;
401
402 /* Make it an internal plugin */
403 GMPC_PLUGIN_BASE(self)->plugin_type = GMPC_INTERNALL;
404 playlist3_message_init(self);
405
406 return G_OBJECT(self);
407 }
408
playlist3_message_plugin_class_init(Playlist3MessagePluginClass * klass)409 static void playlist3_message_plugin_class_init(Playlist3MessagePluginClass * klass)
410 {
411 /* Connect destroy and construct */
412 G_OBJECT_CLASS(klass)->finalize = playlist3_message_plugin_finalize;
413 G_OBJECT_CLASS(klass)->constructor = playlist3_message_plugin_constructor;
414
415 /* Connect plugin functions */
416 GMPC_PLUGIN_BASE_CLASS(klass)->get_version = playlist3_message_plugin_get_version;
417 GMPC_PLUGIN_BASE_CLASS(klass)->get_name = playlist3_message_plugin_get_name;
418
419 }
420
playlist3_message_plugin_get_type(void)421 GType playlist3_message_plugin_get_type(void)
422 {
423 static GType playlist3_message_plugin_type_id = 0;
424 if (playlist3_message_plugin_type_id == 0)
425 {
426 static const GTypeInfo info = {
427 .class_size = sizeof(Playlist3MessagePluginClass),
428 .class_init = (GClassInitFunc) playlist3_message_plugin_class_init,
429 .instance_size = sizeof(Playlist3MessagePlugin),
430 .n_preallocs = 0
431 };
432 playlist3_message_plugin_type_id =
433 g_type_register_static(GMPC_PLUGIN_TYPE_BASE, "Playlist3MessagesPlugin", &info, 0);
434 }
435 return playlist3_message_plugin_type_id;
436 }
437
playlist3_message_plugin_new(void)438 Playlist3MessagePlugin *playlist3_message_plugin_new(void)
439 {
440 return g_object_newv(playlist3_message_plugin_get_type(), 0, NULL);
441 }
442
443 /**
444 * Old compatibility functions
445 */
playlist3_show_error_message(const gchar * message,ErrorLevel el)446 void playlist3_show_error_message(const gchar * message, ErrorLevel el)
447 {
448 playlist3_message_show(pl3_messages, message, el);
449 }
450
playlist3_close_error(void)451 void playlist3_close_error(void)
452 {
453 playlist3_message_close(pl3_messages);
454 }
455
playlist3_error_add_widget(GtkWidget * widget)456 void playlist3_error_add_widget(GtkWidget * widget)
457 {
458 playlist3_message_add_widget(pl3_messages, widget);
459 }
460
message_window_open(void)461 void message_window_open(void)
462 {
463 playlist3_message_window_open(pl3_messages);
464 }
465
466 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */
467