1 /*
2 * Copyright (C) 2016 Jens Georg <mail@jensge.org>
3 *
4 * Authors: Jens Georg <mail@jensge.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21 #include <glib/gi18n.h>
22 #include <libgupnp-av/gupnp-av.h>
23
24 #include <string.h>
25
26 #include "entry-completion.h"
27 #include "search-dialog.h"
28 #include "server-device.h"
29 #include "didl-dialog.h"
30 #include "icons.h"
31
32 /* DLNA recommends something between 10 and 30, let's just use 30
33 * cf. 7.4.1.4.10.9 in the 2014 version of the architecture and protocols
34 * guideline
35 * dlna-guidelines-march-2014---part-1-1-architectures-and-protocols.pdf
36 */
37 #define SEARCH_DIALOG_DEFAULT_SLICE 30
38
39 #define DEFAULT_SEARCH_FILTER "upnp:class,dc:title"
40
41 #define DIALOG_RESOURCE_PATH "/org/gupnp/Tools/AV-CP/search-dialog.ui"
42
43 typedef struct _SearchTask SearchTask;
44
45 struct _SearchDialog {
46 GtkDialog parent;
47 };
48
49 struct _SearchDialogClass {
50 GtkDialogClass parent_class;
51 };
52
53 struct _SearchDialogPrivate {
54 GtkListStore *search_dialog_liststore;
55 GtkEntry *search_dialog_entry;
56 GtkTreeView *search_dialog_treeview;
57 char *id;
58 char *title;
59 AVCPMediaServer *server;
60 guint pulse_timer;
61 SearchTask *task;
62 GUPnPSearchCriteriaParser *parser;
63 GRegex *position_re;
64 GtkWidget *popup_menu;
65 };
66
67 typedef struct _SearchDialogPrivate SearchDialogPrivate;
68 G_DEFINE_TYPE_WITH_PRIVATE (SearchDialog, search_dialog, GTK_TYPE_DIALOG)
69
70 static void
71 search_dialog_on_search_activate (SearchDialog *self, GtkEntry *entry);
72
73 static gboolean
74 search_dialog_on_listview_button_release (GtkWidget *widget,
75 GdkEventButton *event,
76 gpointer user_data);
77
78 static void
79 search_dialog_on_didl_popup_activate (SearchDialog *self, GVariant *parameter, gpointer user_data);
80
81 static void
82 search_dialog_on_icon_release (SearchDialog *self, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data);
83
84 static void
85 search_dialog_finalize (GObject *object);
86
87 static void
88 search_dialog_dispose (GObject *object);
89
90 struct _SearchTask {
91 AVCPMediaServer *server;
92 char *search_expression;
93 char *container_id;
94 GtkListStore *target;
95 int start;
96 int count;
97 int total;
98 gboolean running;
99 GUPnPDIDLLiteParser *parser;
100 GError *error;
101 GSourceFunc callback;
102 gpointer user_data;
103 GCancellable *cancellable;
104 };
105
106 static void
107 search_task_on_didl_object_available (GUPnPDIDLLiteParser *parser,
108 GUPnPDIDLLiteObject *object,
109 gpointer user_data);
110
111 static SearchTask *
search_task_new(AVCPMediaServer * server,GtkListStore * target,const char * container_id,const char * search_expression,int count,GSourceFunc callback,gpointer user_data)112 search_task_new (AVCPMediaServer *server,
113 GtkListStore *target,
114 const char *container_id,
115 const char *search_expression,
116 int count,
117 GSourceFunc callback,
118 gpointer user_data)
119 {
120 SearchTask *task = g_new0 (SearchTask, 1);
121
122 task->search_expression = g_strdup (search_expression);
123 task->target = g_object_ref (target);
124 task->server = g_object_ref (server);
125 task->container_id = g_strdup (container_id);
126 task->start = 0;
127 task->count = count;
128 task->total = -1;
129 task->parser = gupnp_didl_lite_parser_new ();
130 task->error = NULL;
131 task->callback = callback;
132 task->user_data = user_data;
133 task->cancellable = g_cancellable_new ();
134
135 g_signal_connect (G_OBJECT (task->parser),
136 "object-available",
137 G_CALLBACK (search_task_on_didl_object_available),
138 task);
139
140 return task;
141 }
142
143 static void
search_task_free(SearchTask * task)144 search_task_free (SearchTask *task) {
145 g_clear_object (&task->target);
146 g_clear_object (&task->server);
147 g_clear_object (&task->parser);
148 g_free (task->search_expression);
149 g_free (task->container_id);
150 if (task->error != NULL) {
151 g_error_free (task->error);
152 }
153 g_free (task);
154 }
155
156 static void
search_task_cancel(SearchTask * task)157 search_task_cancel (SearchTask *task) {
158 g_cancellable_cancel (task->cancellable);
159 }
160
161 static gboolean
search_task_idle_callback(gpointer user_data)162 search_task_idle_callback (gpointer user_data)
163 {
164 SearchTask *task = (SearchTask *)user_data;
165 if (task->callback != NULL) {
166 task->callback (task->user_data);
167 }
168
169 return FALSE;
170 }
171
172 static void
search_task_set_finished(SearchTask * task,GError * error)173 search_task_set_finished (SearchTask *task, GError *error)
174 {
175 task->running = FALSE;
176 task->error = error;
177
178 g_idle_add (search_task_idle_callback, task);
179 }
180
181 static void
search_task_on_search_ready(GObject * source,GAsyncResult * res,gpointer user_data)182 search_task_on_search_ready (GObject *source, GAsyncResult *res, gpointer user_data)
183 {
184 SearchTask *task = (SearchTask *)user_data;
185 GError *error = NULL;
186 char *didl_xml = NULL;
187 guint32 total = 0;
188 guint32 returned = 0;
189 gboolean result;
190 gboolean finished = FALSE;
191
192 result = av_cp_media_server_search_finish (AV_CP_MEDIA_SERVER (source),
193 res,
194 &didl_xml,
195 &total,
196 &returned,
197 &error);
198
199 g_debug ("Received search slice result for %s with expression %s, result is %s",
200 task->container_id,
201 task->search_expression,
202 result ? "TRUE" : "FALSE");
203
204
205 if (!result) {
206 finished = TRUE;
207
208 goto out;
209 }
210
211 if (g_cancellable_is_cancelled (task->cancellable)) {
212 finished = TRUE;
213
214 goto out;
215 }
216
217 /* Nothing returned by the server */
218 if (returned == 0) {
219 finished = TRUE;
220
221 goto out;
222 }
223
224 if (didl_xml == NULL) {
225 finished = TRUE;
226 goto out;
227 }
228
229 gupnp_didl_lite_parser_parse_didl (task->parser, didl_xml, &error);
230 if (error != NULL) {
231 finished = TRUE;
232
233 goto out;
234 }
235
236 if (total != 0) {
237 task->total = total;
238 }
239
240 task->start += returned;
241
242 out:
243 g_clear_pointer (&didl_xml, g_free);
244 if (finished) {
245 g_debug ("Finished search, error: %s",
246 error ? error->message : "none");
247 search_task_set_finished (task, error);
248 } else {
249 g_debug ("Starting new slice %u/%u (total %u)",
250 task->start,
251 task->count,
252 task->total == -1 ? 0 : task->total);
253
254 av_cp_media_server_search_async (task->server,
255 task->cancellable,
256 search_task_on_search_ready,
257 task->container_id,
258 task->search_expression,
259 DEFAULT_SEARCH_FILTER,
260 task->start,
261 task->count,
262 task);
263 }
264 }
265
266 static void
search_task_run(SearchTask * task)267 search_task_run (SearchTask *task) {
268 if (task->running) {
269 g_debug ("Search task is already running, not doing anything.");
270
271 return;
272 }
273
274 g_debug ("Starting search task for %s with expression %s",
275 task->container_id,
276 task->search_expression);
277
278 task->running = TRUE;
279
280 av_cp_media_server_search_async (task->server,
281 NULL,
282 search_task_on_search_ready,
283 task->container_id,
284 task->search_expression,
285 DEFAULT_SEARCH_FILTER,
286 task->start,
287 task->count,
288 task);
289 }
290
291 #define ITEM_CLASS_IMAGE "object.item.imageItem"
292 #define ITEM_CLASS_AUDIO "object.item.audioItem"
293 #define ITEM_CLASS_VIDEO "object.item.videoItem"
294 #define ITEM_CLASS_TEXT "object.item.textItem"
295 #define CONTAINER_CLASS "object.container"
296
297 static GdkPixbuf *
get_item_icon(GUPnPDIDLLiteObject * object)298 get_item_icon (GUPnPDIDLLiteObject *object)
299 {
300 GdkPixbuf *icon;
301 const char *class_name;
302
303 class_name = gupnp_didl_lite_object_get_upnp_class (object);
304 if (G_UNLIKELY (class_name == NULL)) {
305 return get_icon_by_id (ICON_FILE);
306 }
307
308 if (g_str_has_prefix (class_name, ITEM_CLASS_IMAGE)) {
309 icon = get_icon_by_id (ICON_IMAGE_ITEM);
310 } else if (g_str_has_prefix (class_name, ITEM_CLASS_AUDIO)) {
311 icon = get_icon_by_id (ICON_AUDIO_ITEM);
312 } else if (g_str_has_prefix (class_name, ITEM_CLASS_VIDEO)) {
313 icon = get_icon_by_id (ICON_VIDEO_ITEM);
314 } else if (g_str_has_prefix (class_name, ITEM_CLASS_TEXT)) {
315 icon = get_icon_by_id (ICON_TEXT_ITEM);
316 } else if (g_str_has_prefix (class_name, CONTAINER_CLASS) ){
317 icon = get_icon_by_id (ICON_CONTAINER);
318 } else {
319 icon = get_icon_by_id (ICON_FILE);
320 }
321
322 return icon;
323 }
324
325 static void
search_task_on_didl_object_available(GUPnPDIDLLiteParser * parser,GUPnPDIDLLiteObject * object,gpointer user_data)326 search_task_on_didl_object_available (GUPnPDIDLLiteParser *parser,
327 GUPnPDIDLLiteObject *object,
328 gpointer user_data)
329 {
330 SearchTask *task = (SearchTask *)user_data;
331 GtkTreeIter iter;
332
333 gtk_list_store_insert_with_values (task->target,
334 &iter,
335 -1,
336 0, get_item_icon (object),
337 1, gupnp_didl_lite_object_get_title (object),
338 3, gupnp_didl_lite_object_get_id (object),
339 -1);
340 }
341
342 static void
search_dialog_class_init(SearchDialogClass * klass)343 search_dialog_class_init (SearchDialogClass *klass)
344 {
345 GObjectClass *object_class = G_OBJECT_CLASS (klass);
346 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
347
348 gtk_widget_class_set_template_from_resource (widget_class,
349 DIALOG_RESOURCE_PATH);
350 gtk_widget_class_bind_template_child_private (widget_class,
351 SearchDialog,
352 search_dialog_liststore);
353 gtk_widget_class_bind_template_child_private (widget_class,
354 SearchDialog,
355 search_dialog_entry);
356 gtk_widget_class_bind_template_child_private (widget_class,
357 SearchDialog,
358 search_dialog_treeview);
359 gtk_widget_class_bind_template_callback (widget_class,
360 search_dialog_on_search_activate);
361 gtk_widget_class_bind_template_callback (widget_class,
362 search_dialog_on_listview_button_release);
363 gtk_widget_class_bind_template_callback (widget_class, gtk_widget_hide);
364 gtk_widget_class_bind_template_callback (widget_class, search_dialog_on_icon_release);
365
366
367 object_class->finalize = search_dialog_finalize;
368 object_class->dispose = search_dialog_dispose;
369 }
370
371 static void
search_dialog_init(SearchDialog * self)372 search_dialog_init (SearchDialog *self)
373 {
374 SearchDialogPrivate *priv = NULL;
375
376 gtk_widget_init_template (GTK_WIDGET (self));
377 priv = search_dialog_get_instance_private (self);
378
379 priv->parser = gupnp_search_criteria_parser_new ();
380 gtk_entry_set_completion (priv->search_dialog_entry,
381 entry_completion_new ());
382
383 GMenu *menu = g_menu_new ();
384 g_menu_insert (menu, 0, _("Show _DIDL…"), "search.show-didl");
385 priv->popup_menu = gtk_menu_new_from_model (G_MENU_MODEL (menu));
386 g_object_unref (menu);
387
388 gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
389 GTK_WIDGET (self),
390 NULL);
391 GSimpleActionGroup *group = g_simple_action_group_new ();
392 GSimpleAction *action = g_simple_action_new ("show-didl", NULL);
393 g_signal_connect_swapped (G_OBJECT (action),
394 "activate",
395 G_CALLBACK (search_dialog_on_didl_popup_activate),
396 self);
397 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
398
399 gtk_widget_insert_action_group (GTK_WIDGET (self),
400 "search",
401 G_ACTION_GROUP (group));
402 g_object_unref (group);
403 g_object_unref (action);
404 }
405
406 static void
search_dialog_dispose(GObject * object)407 search_dialog_dispose (GObject *object)
408 {
409 SearchDialog *self = SEARCH_DIALOG (object);
410 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
411 GObjectClass *parent_class =
412 G_OBJECT_CLASS (search_dialog_parent_class);
413
414 if (priv->task != NULL) {
415 search_task_cancel (priv->task);
416 }
417
418 if (priv->pulse_timer != 0) {
419 g_source_remove (priv->pulse_timer);
420 priv->pulse_timer = 0;
421 }
422
423 g_clear_object (&priv->parser);
424 g_clear_object (&priv->popup_menu);
425
426 if (parent_class->dispose != NULL) {
427 parent_class->dispose (object);
428 }
429 }
430
431 static void
search_dialog_finalize(GObject * object)432 search_dialog_finalize (GObject *object)
433 {
434 SearchDialog *self = SEARCH_DIALOG (object);
435 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
436 GObjectClass *parent_class =
437 G_OBJECT_CLASS (search_dialog_parent_class);
438
439 g_clear_pointer (&priv->id, g_free);
440 g_clear_pointer (&priv->title, g_free);
441 g_clear_pointer (&priv->task, search_task_free);
442 g_clear_pointer (&priv->position_re, g_regex_unref);
443
444 if (parent_class->finalize != NULL) {
445 parent_class->finalize (object);
446 }
447 }
448
449 static gboolean
search_dialog_on_search_task_done(gpointer user_data)450 search_dialog_on_search_task_done (gpointer user_data)
451 {
452 SearchDialog *self = SEARCH_DIALOG (user_data);
453 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
454
455 g_source_remove (priv->pulse_timer);
456 gtk_entry_set_progress_fraction (priv->search_dialog_entry, 0);
457 gtk_editable_set_editable (GTK_EDITABLE (priv->search_dialog_entry), TRUE);
458 g_object_set (G_OBJECT (priv->search_dialog_entry),
459 "primary-icon-name",
460 "edit-find-symbolic",
461 "secondary-icon-activatable",
462 TRUE,
463 NULL);
464
465 /* Only show visible error if dialog is visible. */
466 if (priv->task->error != NULL &&
467 !g_error_matches (priv->task->error,
468 G_IO_ERROR,
469 G_IO_ERROR_CANCELLED) &&
470 gtk_widget_is_visible (GTK_WIDGET (self))) {
471 GtkWidget *dialog = NULL;
472
473 dialog = gtk_message_dialog_new (GTK_WINDOW (self),
474 GTK_DIALOG_DESTROY_WITH_PARENT,
475 GTK_MESSAGE_WARNING,
476 GTK_BUTTONS_CLOSE,
477 "%s",
478 _("Search failed"));
479
480 gtk_message_dialog_format_secondary_text
481 (GTK_MESSAGE_DIALOG (dialog),
482 _("Error message was: %s"),
483 priv->task->error->message);
484 gtk_dialog_run (GTK_DIALOG (dialog));
485 gtk_widget_destroy (dialog);
486
487 g_critical ("Failed to search: %s", priv->task->error->message);
488 }
489
490 g_clear_pointer (&priv->task, search_task_free);
491
492 return FALSE;
493 }
494
495 void
search_dialog_set_server(SearchDialog * self,AVCPMediaServer * server)496 search_dialog_set_server (SearchDialog *self, AVCPMediaServer *server)
497 {
498 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
499 GtkEntryCompletion *completion = gtk_entry_get_completion (priv->search_dialog_entry);
500
501 priv->server = server;
502
503 entry_completion_set_search_criteria (ENTRY_COMPLETION (completion),
504 av_cp_media_server_get_search_caps (server));
505 }
506
507 void
search_dialog_set_container_id(SearchDialog * self,char * id)508 search_dialog_set_container_id (SearchDialog *self, char *id)
509 {
510 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
511 g_free (priv->id);
512
513 priv->id = id;
514 }
515
516 void
search_dialog_set_container_title(SearchDialog * self,char * title)517 search_dialog_set_container_title (SearchDialog *self, char *title)
518 {
519 char *name = NULL;
520 char *window_title = NULL;
521
522 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
523 g_free (priv->title);
524
525 priv->title = title;
526 name = gupnp_device_info_get_friendly_name
527 (GUPNP_DEVICE_INFO (priv->server));
528
529 if (g_str_equal (priv->id, "0")) {
530 window_title = g_strdup_printf (_("Searching on %s"),
531 name);
532 } else {
533 window_title = g_strdup_printf (_("Searching in %s on %s"),
534 title,
535 name);
536 }
537
538 gtk_window_set_title (GTK_WINDOW (self), window_title);
539
540 g_free (name);
541 g_free (window_title);
542 }
543
544 void
search_dialog_run(SearchDialog * self)545 search_dialog_run (SearchDialog *self)
546 {
547 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
548 gtk_dialog_run (GTK_DIALOG (self));
549 gtk_widget_hide (GTK_WIDGET (self));
550
551 if (priv->task != NULL &&
552 priv->task->running) {
553 search_task_cancel (priv->task);
554 }
555 }
556
557 static gboolean
pulse_timer(gpointer user_data)558 pulse_timer (gpointer user_data)
559 {
560 SearchDialog *self = SEARCH_DIALOG (user_data);
561 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
562 if (priv->task->total == -1) {
563 gtk_entry_progress_pulse (GTK_ENTRY (priv->search_dialog_entry));
564 } else {
565 gdouble progress = (gdouble) priv->task->start /
566 (gdouble) priv->task->total;
567 gtk_entry_set_progress_fraction
568 (GTK_ENTRY (priv->search_dialog_entry),
569 progress);
570 }
571
572 return TRUE;
573 }
574
575 static void
search_dialog_on_search_activate(SearchDialog * self,GtkEntry * entry)576 search_dialog_on_search_activate (SearchDialog *self, GtkEntry *entry)
577 {
578 GError *error = NULL;
579 const char *text = gtk_entry_get_text (entry);
580 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
581
582 gupnp_search_criteria_parser_parse_text (priv->parser, text, &error);
583 if (error == NULL) {
584
585 g_object_set (G_OBJECT (entry),
586 "primary-icon-name",
587 "media-playback-stop-symbolic",
588 "secondary-icon-activatable",
589 FALSE,
590 NULL);
591 gtk_list_store_clear (priv->search_dialog_liststore);
592 gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
593 priv->pulse_timer = g_timeout_add_seconds (1, pulse_timer, self);
594
595 g_clear_pointer (&priv->task, search_task_free);
596
597 priv->task = search_task_new (priv->server,
598 priv->search_dialog_liststore,
599 priv->id,
600 gtk_entry_get_text (entry),
601 SEARCH_DIALOG_DEFAULT_SLICE,
602 search_dialog_on_search_task_done,
603 self);
604 search_task_run (priv->task);
605 } else {
606 GtkWidget *dialog = NULL;
607 GMatchInfo *info = NULL;
608 char *position = NULL;
609
610 if (priv->position_re == NULL) {
611 priv->position_re = g_regex_new ("([0-9]+)$", 0, 0, NULL);
612 }
613
614 if (!g_regex_match (priv->position_re, error->message, 0, &info)) {
615 position = g_strdup ("-1");
616 } else {
617 position = g_match_info_fetch (info, 0);
618 }
619
620 g_match_info_free (info);
621
622 dialog = gtk_message_dialog_new (GTK_WINDOW (self),
623 GTK_DIALOG_DESTROY_WITH_PARENT,
624 GTK_MESSAGE_WARNING,
625 GTK_BUTTONS_CLOSE,
626 "%s",
627 _("Search failed"));
628
629 gtk_message_dialog_format_secondary_text
630 (GTK_MESSAGE_DIALOG (dialog),
631 _("Search criteria invalid: %s"),
632 error->message);
633 gtk_dialog_run (GTK_DIALOG (dialog));
634 gtk_widget_destroy (dialog);
635
636 g_error_free (error);
637 error = NULL;
638
639 gtk_editable_set_position (GTK_EDITABLE (entry),
640 atoi (position));
641 g_free (position);
642
643 return;
644 }
645
646 }
647
648 static void
do_popup_menu(GtkMenu * menu,GtkWidget * widget,GdkEventButton * event)649 do_popup_menu (GtkMenu *menu, GtkWidget *widget, GdkEventButton *event)
650 {
651 #if GTK_CHECK_VERSION(3,22,0)
652 gtk_menu_popup_at_pointer (menu,
653 event != NULL ? (GdkEvent *) event
654 : gtk_get_current_event ());
655 #else
656 int button = 0;
657 int event_time;
658 if (event) {
659 button = event->button;
660 event_time = event->time;
661 } else {
662 event_time = gtk_get_current_event_time ();
663 }
664
665 gtk_menu_popup (menu, NULL, NULL, NULL, NULL, button, event_time);
666 #endif
667 }
668
669 static gboolean
search_dialog_on_listview_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)670 search_dialog_on_listview_button_release (GtkWidget *widget,
671 GdkEventButton *event,
672 gpointer user_data)
673 {
674 SearchDialog *self = SEARCH_DIALOG (user_data);
675 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
676 GtkTreeSelection *selection = NULL;
677 GtkTreeModel *model = NULL;
678 GtkTreeIter iter;
679 GtkTreeView *treeview = priv->search_dialog_treeview;
680
681 if (event->type != GDK_BUTTON_RELEASE || event->button != 3) {
682 return FALSE;
683 }
684
685 selection = gtk_tree_view_get_selection (treeview);
686 g_assert (selection != NULL);
687
688 /* Only show the popup menu when a row is selected */
689 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
690 return FALSE;
691 }
692
693 do_popup_menu (GTK_MENU (priv->popup_menu),
694 GTK_WIDGET (treeview),
695 event);
696
697 return TRUE;
698 }
699
700 static void
on_object(GUPnPDIDLLiteParser * parser,GUPnPDIDLLiteObject * object,gpointer user_data)701 on_object (GUPnPDIDLLiteParser *parser,
702 GUPnPDIDLLiteObject *object,
703 gpointer user_data)
704 {
705 GUPnPDIDLLiteObject **result = (GUPnPDIDLLiteObject **)user_data;
706 if (*result == NULL) {
707 *result = g_object_ref (object);
708 }
709 }
710
711 static void
search_dialog_on_metadata_ready(GObject * source,GAsyncResult * res,gpointer user_data)712 search_dialog_on_metadata_ready (GObject *source,
713 GAsyncResult *res,
714 gpointer user_data)
715 {
716 SearchDialog *self = SEARCH_DIALOG (user_data);
717 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
718 AVCPMediaServer *server = AV_CP_MEDIA_SERVER (source);
719 GtkTreeView *treeview = priv->search_dialog_treeview;
720 GtkTreeModel *model = NULL;
721 GtkTreeSelection *selection = NULL;
722 char *xml;
723 GError *error = NULL;
724 GtkTreeIter iter;
725
726 if (!av_cp_media_server_browse_metadata_finish (server,
727 res,
728 &xml,
729 &error)) {
730 GtkWidget *message = NULL;
731
732 message = gtk_message_dialog_new (GTK_WINDOW (self),
733 GTK_DIALOG_MODAL,
734 GTK_MESSAGE_WARNING,
735 GTK_BUTTONS_CLOSE,
736 _("Error fetching detailed information: %s"),
737 error->message);
738 gtk_dialog_run (GTK_DIALOG (message));
739 gtk_widget_destroy (message);
740 } else {
741 selection = gtk_tree_view_get_selection (treeview);
742 if (gtk_tree_selection_get_selected (selection,
743 &model,
744 &iter)) {
745 GUPnPDIDLLiteParser *parser = NULL;
746 GUPnPDIDLLiteObject *didl_object = NULL;
747
748 parser = gupnp_didl_lite_parser_new ();
749 g_signal_connect (G_OBJECT (parser),
750 "object-available",
751 G_CALLBACK (on_object),
752 &didl_object);
753 gupnp_didl_lite_parser_parse_didl (parser, xml, NULL);
754 gtk_list_store_set (GTK_LIST_STORE (model),
755 &iter,
756 2, didl_object,
757 -1);
758 g_object_unref (parser);
759 g_object_unref (didl_object);
760 {
761 AVCPDidlDialog *dialog = av_cp_didl_dialog_new ();
762
763 av_cp_didl_dialog_set_xml (dialog, xml);
764 gtk_window_set_transient_for (GTK_WINDOW (dialog),
765 GTK_WINDOW (self));
766 gtk_dialog_run (GTK_DIALOG (dialog));
767 gtk_widget_destroy (GTK_WIDGET (dialog));
768 }
769 }
770 g_free (xml);
771 }
772 }
773
774 static void
search_dialog_on_didl_popup_activate(SearchDialog * self,GVariant * parameter,gpointer user_data)775 search_dialog_on_didl_popup_activate (SearchDialog *self, GVariant *parameter, gpointer user_data)
776 {
777 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
778 GtkTreeView *treeview = priv->search_dialog_treeview;
779 GtkTreeModel *model = NULL;
780 GUPnPDIDLLiteObject *didl_object = NULL;
781 GtkTreeSelection *selection = NULL;
782 GtkTreeIter iter;
783 char *id = NULL;
784
785 selection = gtk_tree_view_get_selection (treeview);
786
787 /* Only show the popup menu when a row is selected */
788 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
789 return;
790 }
791
792 gtk_tree_model_get (model,
793 &iter,
794 2, &didl_object,
795 3, &id,
796 -1);
797
798 if (didl_object != NULL) {
799 AVCPDidlDialog *dialog = av_cp_didl_dialog_new ();
800 char *xml = NULL;
801
802 g_free (id);
803 xml = gupnp_didl_lite_object_get_xml_string (didl_object);
804 av_cp_didl_dialog_set_xml (dialog, xml);
805 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self));
806 gtk_dialog_run (GTK_DIALOG (dialog));
807 gtk_widget_destroy (GTK_WIDGET (dialog));
808 g_free (xml);
809 g_object_unref (didl_object);
810 } else {
811 av_cp_media_server_browse_metadata_async (priv->server,
812 NULL,
813 search_dialog_on_metadata_ready,
814 id,
815 self);
816 g_free (id);
817 }
818 }
819
820 GtkDialog *
search_dialog_new(void)821 search_dialog_new (void)
822 {
823 GtkSettings *settings = gtk_settings_get_default ();
824 int use_header;
825
826 g_object_get (G_OBJECT (settings),
827 "gtk-dialogs-use-header",
828 &use_header,
829 NULL);
830
831 return (GtkDialog *) g_object_new (SEARCH_DIALOG_TYPE,
832 "use-header-bar",
833 use_header == 1 ? TRUE : FALSE,
834 NULL);
835 }
836
837 static void
search_dialog_on_icon_release(SearchDialog * self,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)838 search_dialog_on_icon_release (SearchDialog *self, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data)
839 {
840 SearchDialogPrivate *priv = search_dialog_get_instance_private (self);
841
842 if (icon_pos == GTK_ENTRY_ICON_PRIMARY) {
843 if (priv->task != NULL && priv->task->running) {
844 search_task_cancel (priv->task);
845 } else {
846 search_dialog_on_search_activate (self, user_data);
847 }
848 } else {
849 gtk_entry_set_text (GTK_ENTRY (user_data), "");
850 }
851 }
852