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