Remove GmpcMetaWatcher.match_data function.
[gmpc.git] / src / gmpc-meta-text-view.gob
blobabab871ccfb8203264deceea1894fae86c9cc0f3
1 /* Gnome M usic Player Client (GMPC)
2  * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3  * Project homepage: http://gmpclient.org/
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.
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.
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.
20 requires 2.0.0
22 %headertop{
23 #include <gtk/gtk.h>
24 #include <glib/gstdio.h>
25 #include <libmpd/libmpd.h>
26 #include <unistd.h>
27 #include "misc.h"
29 %ph{
30 #include "gmpc-extras.h"
31 #include "main.h"
34 class Gmpc:Meta:Text:View from Gtk:Text:View {
35         private GtkTextBuffer *buffer   = {gtk_text_buffer_new(NULL)} unrefwith g_object_unref;
36         private mpd_Song *song                  = {NULL} destroywith mpd_freeSong;
37         private MetaDataType type                               = {META_ARTIST_TXT};
38         private gulong meta_id                  = {0};
39     private GtkWidget *edit         = {NULL};
40     private GtkWidget *cancel       = {NULL};
41     private MetaData *met = {NULL} destroywith meta_data_free;
43     public gboolean force_ro       = {FALSE};
44     public gboolean use_monospace       = {FALSE};
47     private gboolean
48     key_press_event(self, GdkEventKey *key, gpointer user_data)
49     {
50         if(key->keyval == GDK_Escape)
51         {
52             if(self->_priv->cancel){
53                 gtk_widget_activate(self->_priv->cancel);
54             }
55             return FALSE;
56         }
57         return FALSE;
58     }
60         init (self)
61         {
62                 gtk_text_view_set_buffer(GTK_TEXT_VIEW(self), self->_priv->buffer);
63                 gtk_text_view_set_editable(GTK_TEXT_VIEW(self), FALSE);
64                 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(self), GTK_WRAP_WORD);
65         g_signal_connect(G_OBJECT(self), "key-press-event", G_CALLBACK(self_key_press_event), NULL);
67                 g_signal_connect(G_OBJECT(self), "populate-popup", G_CALLBACK(self_menu_populate_callback), NULL);
68                 self->_priv->meta_id= g_signal_connect(G_OBJECT(gmw), "data-changed", G_CALLBACK(self_meta_callback), self);
69         }
71         public
72         Gmpc:Meta:Text:View * new (int type)
73         {
74                 Self *gmi =  GET_NEW;
75                 gmi->_priv->type = type;
76         gtk_text_buffer_create_tag (gmi->_priv->buffer, "not_editable",
77                 "editable", FALSE,
78                 NULL);
80         gtk_text_buffer_create_tag (gmi->_priv->buffer, "monospace",
81                 "family", "Monospace",
82                 "family-set", TRUE,
83                 NULL);
84                 return gmi;
85         }
87         private
88         void
89         menu_populate_callback(self, GtkMenu *menu, gpointer data)
90         {
91                 if(self->_priv->song)
92                 {
93                         GtkWidget *item = gtk_separator_menu_item_new();
94                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
97                         item = gtk_image_menu_item_new_with_label(_("Refetch"));
98                         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), gtk_image_new_from_stock(GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU));
99                         g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(self_query_refetch),self);
100                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
102                         item = gtk_image_menu_item_new_with_label(_("Select file"));
103                         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU));
104                         g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(self_select_file),self);
105                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
107                         item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLEAR, NULL);
108                         g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(self_clear_entry),self);
109                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
111                         item = gtk_image_menu_item_new_with_label(_("Metadata selector"));
112                         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU));
113                         g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(self_select_metadata_editor),self);
114                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
116                         gtk_widget_show_all(GTK_WIDGET(menu));
117                 }
118         }
120     private void
121     select_metadata_editor(self)
122     {
123                 gmpc_meta_data_edit_window_new(self->_priv->song,self->_priv->type);
124     }
126         public
127         void
128         query_refetch(self)
129         {
130                 if(self->_priv->song)
131                 {
132                         MetaData *met= NULL;
133                         MetaDataResult ret;
134                         ret = meta_data_get_path(self->_priv->song, self->_priv->type|META_QUERY_NO_CACHE, &met, NULL, NULL);
135                         if(ret == META_DATA_FETCHING) {
136                                 self_set_text_fetching(self);
137                         }else if (ret == META_DATA_AVAILABLE) {
138                 self_set_text_from_metadata(self, met);
139             } else {
140                                 self_set_text_na(self);
141                         }
142                         if(met)
143                         {
144                                 meta_data_free(met);
145                         }
146                 }
147         }
149         public
150         void
151         query_text_from_song(self, mpd_Song *song)
152     {
153         MetaData *met = NULL;
154         MetaDataResult ret;
156         if(self->_priv->song)
157         {
158             mpd_freeSong(self->_priv->song);
159         }
160         self->_priv->song = mpd_songDup(song);
163         ret = meta_data_get_path(self->_priv->song, self->_priv->type, &met, NULL, NULL);
164         if(ret == META_DATA_FETCHING)
165         {
166             self_set_text_fetching(self);
167         }else if (ret == META_DATA_AVAILABLE) {
168             self_set_text_from_metadata(self,met);
169         } else {
170             self_set_text_na(self);
171         }
172         if(met)
173         {
174             meta_data_free(met);
175         }
176     }
178     private void
179     cancel_button_clicked(self, GtkWidget *button)
180     {
181         gtk_text_view_set_editable(GTK_TEXT_VIEW(self), FALSE);
182         if(self->_priv->met)
183         {
184             MetaData *met = self->_priv->met;
185             self->_priv->met = NULL;
186             /* Create a copy as self_set_from_path modifies self->_priv->path */
187             self_set_text_from_metadata(self, met);
188             meta_data_free(met);
189         }else self_set_text_na(self);
190     }
192     private void
193     edit_button_clicked(self, GtkWidget *button)
194     {
195         if(gtk_text_view_get_editable(GTK_TEXT_VIEW(self)))
196         {
197             MetaData *met;
198             gchar *content = NULL;
199             GtkTextIter iter_start, iter_end;
201             /* Get start and end of text, and store it */
202             gtk_text_buffer_get_start_iter(self->_priv->buffer, &iter_start);
203             gtk_text_buffer_get_end_iter(self->_priv->buffer, &iter_end);
204             content = gtk_text_buffer_get_text(self->_priv->buffer, &iter_start, &iter_end, FALSE);
205             gtk_text_view_set_editable(GTK_TEXT_VIEW(self), FALSE);
207             /* Copy path, because signal callback my destroy path first */
208             met = meta_data_new();
209             met->plugin_name = g_strdup("User set");
210             met->type = self->_priv->type;
211             if(content){
212                 met->content_type = META_DATA_CONTENT_TEXT;
213                 met->content = g_strstrip(content);met->size = -1;
214             }else {
215                 met->content_type = META_DATA_CONTENT_EMPTY;
216             }
217                         meta_data_set_entry(self->_priv->song, met);
218             meta_data_free(met);
220         }else{
221             gtk_widget_show(self->_priv->cancel);
222             gtk_text_view_set_editable(GTK_TEXT_VIEW(self), TRUE);
223             gtk_button_set_label(GTK_BUTTON(button), GTK_STOCK_SAVE);
224         }
226     }
227     private
228     void
229     set_text(self,const gchar *text, gsize length, int ro)
230     {
231         GtkTextIter iter;
232         GtkTextIter end_iter;
233         gtk_text_buffer_set_text(self->_priv->buffer, text, length);
235         gtk_text_buffer_get_start_iter(self->_priv->buffer, &iter);
236         gtk_text_buffer_get_end_iter(self->_priv->buffer, &end_iter);
237         if(self->use_monospace) {
238             gtk_text_buffer_apply_tag_by_name(self->_priv->buffer, "monospace",&iter, &end_iter);
239         }
240         if(!ro && !self->force_ro)
241         {
242             GtkTextChildAnchor *anch;
243             self->_priv->edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
244             self->_priv->cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
246             gtk_text_view_set_editable(GTK_TEXT_VIEW(self), FALSE);
247             gtk_widget_set_no_show_all(self->_priv->cancel, TRUE);
248             /* Add 2 empty lines at the end, and make it non-editable */
249             gtk_text_buffer_get_end_iter(self->_priv->buffer, &end_iter);
250             gtk_text_buffer_insert_with_tags_by_name(self->_priv->buffer, &end_iter, "\n\n", -1,"not_editable", NULL);
252             /* Add edit widget */
253             anch = gtk_text_buffer_create_child_anchor(self->_priv->buffer, &end_iter);
254             gtk_text_buffer_get_iter_at_child_anchor(self->_priv->buffer, &iter,anch);
255             /* Make widget non-editable */
256             gtk_text_buffer_apply_tag_by_name(self->_priv->buffer, "not_editable",&iter, &end_iter);
257             /* attach widget to anchor */
258             gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(self), self->_priv->edit, anch);
260             /* Add edit widget */
261             gtk_text_buffer_get_end_iter(self->_priv->buffer, &end_iter);
262             anch = gtk_text_buffer_create_child_anchor(self->_priv->buffer, &end_iter);
263             gtk_text_buffer_get_iter_at_child_anchor(self->_priv->buffer, &iter,anch);
264             /* Make widget non-editable */
265             gtk_text_buffer_apply_tag_by_name(self->_priv->buffer, "not_editable",&iter, &end_iter);
266             /* attach widget to anchor */
267             gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(self), self->_priv->cancel, anch);
269             /* show button */
270             gtk_widget_show(self->_priv->edit);
271             g_signal_connect_swapped(self->_priv->edit, "clicked",G_CALLBACK(self_edit_button_clicked), self);
272             g_signal_connect_swapped(self->_priv->cancel, "clicked",G_CALLBACK(self_cancel_button_clicked), self);
273         }
274         gtk_text_buffer_get_start_iter(self->_priv->buffer, &iter);
275         gtk_text_buffer_place_cursor(self->_priv->buffer, &iter);
276     }
278         signal last NONE (STRING)
279         void
280         set_text_from_path(self,const gchar *path)
281         {
282                 gchar *content=NULL;
283                 gsize size = 0;
284                 if(g_file_get_contents (path,&content,&size,NULL))
285                 {
286                         gchar *new_data = NULL;
287                         gsize new_size = 0;
288                         if(g_utf8_validate(content, size, NULL))
289                         {
290                 self_set_text(self, content, size, FALSE);
291                         }
292                         else
293                         {
294                                 new_data = g_locale_to_utf8(content, size, NULL, &new_size, NULL);
295                                 if(new_data)
296                                 {
297                     self_set_text(self, new_data, new_size,FALSE);
298                                         g_free(new_data);
299                                 }
300                                 else
301                                 {
302                                         new_data = g_strdup_printf("%s: '%s' %s", _("Failed to open file:"), path,_("because of encoding issues"));
303                     self_set_text(self, new_data, -1,TRUE);
304                                         g_free(new_data);
305                                 }
306                         }
307                         g_free(content);
308                 }
309         }
311     private
312     void
313     set_text_from_metadata(self, MetaData *met)
314     {
315         if(met->content_type == META_DATA_CONTENT_URI) {
316             const gchar *path = meta_data_get_uri(met);
317             self_set_text_from_path(self, path);
318         }else if(met->content_type == META_DATA_CONTENT_TEXT) {
319             const gchar *text = meta_data_get_text(met);
320             self_set_text(self,text, -1, FALSE);
321         }else if (met->content_type == META_DATA_CONTENT_HTML) {
322             /* TODO: make utf-8 compatible */
323             gchar *text = meta_data_get_text_from_html(met);
324             if(text)
325             {
326                 /* Get byte length */
327                 self_set_text(self,text, -1, FALSE);
328                 g_free(text);
329             }else{
330                 self_set_text_na(self);
331             }
332         }
333         if(self->_priv->met) meta_data_free(self->_priv->met);
334         self->_priv->met = meta_data_dup(met);
335     }
337         signal last NONE (NONE)
338         void
339         set_text_fetching(self)
340         {
341         if(self->_priv->met) meta_data_free(self->_priv->met);
342         self->_priv->met = NULL;
343                 if(self->_priv->type == META_SONG_TXT)
344                 {
345             self_set_text(self, _("Fetching Lyrics"), -1,TRUE);
346                 }
347                 else if (self->_priv->type == META_ARTIST_TXT)
348                 {
349             self_set_text(self, _("Fetching Artist Info"), -1,TRUE);
350                 }
351         else if (self->_priv->type == META_SONG_GUITAR_TAB)
352         {
353             self_set_text(self, _("Fetching Guitar tab"), -1,TRUE);
354         }
355                 else
356                 {
357             self_set_text(self, _("Fetching Album Info"), -1,TRUE);
358                 }
359         }
361         signal last NONE (NONE)
362         void
363         set_text_na(self)
364         {
365         if(self->_priv->met) meta_data_free(self->_priv->met);
366         self->_priv->met = NULL;
368         self_set_text(self, _("Not Available"), -1,FALSE);
369         }
371         private
372         void
373         meta_callback(GmpcMetaWatcher *mw , mpd_Song *song,  MetaDataType type, MetaDataResult ret, MetaData *met,gpointer data)
374         {
375                 Self *self = data;
376                 /**
377                  * Check for fields
378                  */
379                 if(self->_priv->type != type)
380                         return;
382         // Compare if callback is about 'our' song.
383         // TODO: optimize, by keeping checksum of current song around?
384         {
385             char *ck_a = mpd_song_checksum_type(self->_priv->song, self->_priv->type);
386             char *ck_b = mpd_song_checksum_type(song, self->_priv->type);
387             if(ck_a == NULL || ck_b == NULL || strcmp(ck_a, ck_b) != 0) {
388                 g_free(ck_a);
389                 g_free(ck_b);
390                 return;
391             }
392             g_free(ck_a);
393             g_free(ck_b);
394         }
396                 if(ret == META_DATA_AVAILABLE) {
397             self_set_text_from_metadata(self,met);
398         } else if (ret == META_DATA_FETCHING) {
399                         self_set_text_fetching(self);
400                 } else {
401                         self_set_text_na(self);
402                 }
403         }
404         override (G:Object)
405         void
406         finalize (G:Object *obj)
407         {
409                 PARENT_HANDLER(obj);
410         }
411         override (G:Object)
412         void
413         dispose (G:Object *obj)
414         {
415                 Self *self = GMPC_META_TEXT_VIEW(obj);
416                 if(self->_priv->meta_id)
417                 {
418                         g_signal_handler_disconnect(G_OBJECT(gmw),self->_priv->meta_id);
419                         self->_priv->meta_id =  0;
420                 }
422                 PARENT_HANDLER(obj);
423         }
424         public
425         void
426         clear_entry(self)
427         {
428                 meta_data_clear_entry(self->_priv->song, self->_priv->type);
429         }
430         public
431         void
432         select_file(self)
433         {
434         gchar *p;
435                 mpd_Song *song = mpd_songDup(self->_priv->song);
436                 MetaDataType type = self->_priv->type;
437                 GtkFileFilter *gff = gtk_file_filter_new();
438                 GtkWidget *fcd = gtk_file_chooser_dialog_new(_("Select File"),NULL,
439                                          GTK_FILE_CHOOSER_ACTION_OPEN,
440                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
441                                       GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
442                                       NULL);
443                 gtk_file_filter_set_name(gff, _("Text Document"));
444                 gtk_file_filter_add_mime_type(gff, "text/plain");
445                 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fcd), gff);
446                 gff = gtk_file_filter_new();
447                 gtk_file_filter_set_name(gff, _("All"));
448                 gtk_file_filter_add_pattern(gff, "*");
449                 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fcd), gff);
451         p = cfg_get_single_value_as_string(config, "MetaData", "text-file-chooser");
452         if(p) {
453             gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fcd), p);
454             g_free(p);
455         }
456                 gtk_widget_show_all(fcd);
457                 switch(gtk_dialog_run(GTK_DIALOG(fcd)))
458                 {
459                         case GTK_RESPONSE_ACCEPT:
460                                 {
461                                         gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fcd));
462                     MetaData *met = meta_data_new();
463                     met->type = type;
464                     met->plugin_name = g_strdup("User set");
465                     met->content_type = META_DATA_CONTENT_URI;
466                     met->content = filename; met->size =-1;
468                                         meta_data_set_entry(self->_priv->song, met);
469                                         cfg_set_single_value_as_string(config, "MetaData", "text-file-chooser", filename);
471                     meta_data_free(met);
472                 }
473             default:
474                                 break;
475                 }
476                 gtk_widget_destroy(fcd);
477                 mpd_freeSong(song);
479         }