1 /*
2 Quick Search Plugin for DeaDBeeF audio player
3 Copyright (C) 2014 Christian Boxdörfer <christian.boxdoerfer@posteo.de>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (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 <sys/types.h>
21 #include <sys/stat.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <deadbeef/deadbeef.h>
28 #include <deadbeef/gtkui_api.h>
29
30 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
31 #define trace(fmt,...)
32
33 #define CONFSTR_APPEND_SEARCH_STRING "quick_search.append_search_string"
34 #define CONFSTR_SEARCH_IN "quick_search.search_in"
35 #define CONFSTR_AUTOSEARCH "quick_search.autosearch"
36 #define CONFSTR_HISTORY_SIZE "quick_search.history_size"
37
38 static DB_misc_t plugin;
39 static DB_functions_t *deadbeef = NULL;
40 static ddb_gtkui_t *gtkui_plugin = NULL;
41 static GtkWidget *searchentry = NULL;
42 static int search_delay_timer = 0;
43 static ddb_playlist_t *last_active_plt = NULL;
44
45 static gboolean new_plt_button_state = FALSE;
46 static ddb_playlist_t *added_plt = NULL;
47 static int history_entries = 0;
48 static const char *uuid = "779e2992-3e6e-40d4-9f2e-de06466142a0";
49 static char cache_path[PATH_MAX];
50 static int cache_path_size;
51
52 // search in modes
53 enum search_in_mode_t {
54 SEARCH_INLINE = 0,
55 SEARCH_PLAYLIST = 1,
56 SEARCH_ALL_PLAYLISTS = 2,
57 };
58
59 static int config_search_in = SEARCH_INLINE;
60 static int config_autosearch = TRUE;
61 static int config_append_search_string = FALSE;
62 static int config_history_size = 10;
63
64 typedef struct {
65 ddb_gtkui_widget_t base;
66 GtkWidget *popup;
67 GtkWidget *combo;
68 GtkWidget *clear_history;
69 char *prev_query;
70 } w_quick_search_t;
71
72 static void
73 add_history_query_to_combo (gpointer user_data, const char *text, int prepend);
74
75 static int
check_dir(const char * dir,mode_t mode)76 check_dir (const char *dir, mode_t mode)
77 {
78 char *tmp = strdup (dir);
79 char *slash = tmp;
80 struct stat stat_buf;
81 do
82 {
83 slash = strstr (slash+1, "/");
84 if (slash)
85 *slash = 0;
86 if (-1 == stat (tmp, &stat_buf))
87 {
88 if (0 != mkdir (tmp, mode))
89 {
90 free (tmp);
91 return 0;
92 }
93 }
94 if (slash)
95 *slash = '/';
96 } while (slash);
97 free (tmp);
98 return 1;
99 }
100
101 static int
make_cache_dir(char * path,int size)102 make_cache_dir (char *path, int size)
103 {
104 #if (DDB_API_LEVEL >= 8)
105 const char *cache_dir = deadbeef->get_system_dir (DDB_SYS_DIR_CACHE);
106 #else
107 const char *cache_dir = getenv ("HOME");
108 #endif
109 if (cache_dir) {
110 #if (DDB_API_LEVEL >= 8)
111 const int sz = snprintf (path, size, "%s/quick_search/", cache_dir);
112 #else
113 const int sz = snprintf (path, size, "%s/.cache/deadbeef/quick_search/", cache_dir);
114 #endif
115 if (!check_dir (path, 0755)) {
116 return 0;
117 }
118 return sz;
119 }
120 return 0;
121 }
122
123 static FILE *
get_file_descriptor(const char * path,const char * fname,const char * mode)124 get_file_descriptor (const char *path, const char *fname, const char *mode)
125 {
126 char full_path[PATH_MAX];
127 snprintf (full_path, sizeof (full_path), "%s%s", path, fname);
128 FILE *fp = fopen (full_path, mode);
129 if (!fp) {
130 return NULL;
131 }
132 return fp;
133 }
134
135 static void
load_history_entries(gpointer user_data)136 load_history_entries (gpointer user_data)
137 {
138 w_quick_search_t *w = (w_quick_search_t *)user_data;
139 FILE *fp = get_file_descriptor (cache_path, "history", "r");
140 if (!fp) {
141 return;
142 }
143 char history[1024];
144 while (fgets (history, sizeof (history), fp)) {
145 char *ptr;
146 if ((ptr = strchr(history, '\n')) != NULL) {
147 *ptr = '\0';
148 }
149 if (strcmp (history, "")) {
150 add_history_query_to_combo (w, history, 0);
151 }
152 }
153 fclose (fp);
154 }
155
156 static void
save_history_entries(gpointer user_data)157 save_history_entries (gpointer user_data)
158 {
159 w_quick_search_t *w = (w_quick_search_t *)user_data;
160 FILE *fp = get_file_descriptor (cache_path, "history", "w");
161 if (!fp) {
162 return;
163 }
164 GtkTreeModel *tree = gtk_combo_box_get_model (GTK_COMBO_BOX (w->combo));
165 if (tree) {
166 GtkTreeIter iter;
167 gboolean valid = gtk_tree_model_get_iter_first (tree, &iter);
168 while (valid) {
169 /* Walk through the list, reading each row */
170 gchar *str_data;
171 gtk_tree_model_get (tree, &iter, 0, &str_data, -1);
172 if (strcmp (str_data, "")) {
173 fprintf (fp, "%s\n", str_data);
174 }
175 g_free (str_data);
176 valid = gtk_tree_model_iter_next (tree, &iter);
177 }
178 }
179 fclose (fp);
180 }
181
182 static gboolean
is_quick_search_playlist(ddb_playlist_t * plt)183 is_quick_search_playlist (ddb_playlist_t *plt)
184 {
185 deadbeef->pl_lock ();
186 if (plt && deadbeef->plt_find_meta (plt, "quick_search") != NULL
187 && !strcmp (deadbeef->plt_find_meta (plt, "quick_search"), uuid)) {
188 deadbeef->pl_unlock ();
189 return TRUE;
190 }
191 deadbeef->pl_unlock ();
192 return FALSE;
193 }
194
195 static ddb_playlist_t *
get_last_active_playlist()196 get_last_active_playlist ()
197 {
198 deadbeef->pl_lock ();
199 ddb_playlist_t *plt = NULL;
200 if (!last_active_plt) {
201 plt = deadbeef->plt_get_curr ();
202 }
203 else {
204 // check if playlist got removed
205 int valid = 0;
206 int plt_count = deadbeef->plt_get_count();
207 for (int i = 0; i < plt_count; i++) {
208 ddb_playlist_t *plt_temp = deadbeef->plt_get_for_idx (i);
209 if (!plt_temp) {
210 continue;
211 }
212 if (plt_temp == last_active_plt) {
213 valid = 1;
214 }
215 deadbeef->plt_unref (plt_temp);
216 }
217 if (valid) {
218 plt = last_active_plt;
219 deadbeef->plt_ref (plt);
220 }
221 else {
222 deadbeef->plt_unref (last_active_plt);
223 last_active_plt = NULL;
224 plt = deadbeef->plt_get_curr ();
225 }
226 }
227 deadbeef->pl_unlock ();
228 return plt;
229 }
230
231 static void
set_last_active_playlist(ddb_playlist_t * plt)232 set_last_active_playlist (ddb_playlist_t *plt)
233 {
234 deadbeef->pl_lock ();
235 if (!is_quick_search_playlist (plt) && plt != last_active_plt) {
236 if (last_active_plt) {
237 deadbeef->plt_unref (last_active_plt);
238 }
239 last_active_plt = plt;
240 deadbeef->plt_ref (last_active_plt);
241 }
242 deadbeef->pl_unlock ();
243 }
244
245 static int
add_new_playlist(const char * title)246 add_new_playlist (const char *title) {
247 if (!title) {
248 return -1;
249 }
250 int cnt = deadbeef->plt_get_count ();
251 return deadbeef->plt_add (cnt, title);
252 }
253
254 static int
get_quick_search_playlist()255 get_quick_search_playlist () {
256 // find existing one
257 deadbeef->pl_lock ();
258 int plt_count = deadbeef->plt_get_count();
259 for (int i = 0; i < plt_count; i++) {
260 ddb_playlist_t *plt = deadbeef->plt_get_for_idx (i);
261 if (plt) {
262 if (is_quick_search_playlist (plt)) {
263 deadbeef->plt_unref (plt);
264 deadbeef->pl_unlock ();
265 return i;
266 }
267 deadbeef->plt_unref (plt);
268 }
269 }
270
271 // add new playlist
272 int idx = deadbeef->plt_add (plt_count, "Quick Search");
273 ddb_playlist_t *plt = deadbeef->plt_get_for_idx (idx);
274 // use a uuid to identify quick_search list
275 deadbeef->plt_add_meta (plt, "quick_search", uuid);
276 deadbeef->plt_unref (plt);
277 deadbeef->pl_unlock ();
278 return idx;
279 }
280
281 static void
set_default_quick_search_playlist_title()282 set_default_quick_search_playlist_title ()
283 {
284 deadbeef->pl_lock ();
285 int plt_idx = get_quick_search_playlist ();
286 if (plt_idx >= 0) {
287 ddb_playlist_t *plt = deadbeef->plt_get_for_idx (plt_idx);
288 if (plt) {
289 char new_title[1024] = "";
290 snprintf (new_title, sizeof (new_title), "%s", "Quick Search");
291 deadbeef->plt_set_title (plt, new_title);
292 deadbeef->plt_unref (plt);
293 }
294 }
295 deadbeef->pl_unlock ();
296 }
297
298 static void
append_search_string_to_plt_title(ddb_playlist_t * plt,const char * search_string)299 append_search_string_to_plt_title (ddb_playlist_t *plt, const char *search_string)
300 {
301 if (!search_string || !plt) {
302 return;
303 }
304 deadbeef->pl_lock ();
305 if (plt) {
306 char new_title[1024] = "";
307 if (!strcmp (search_string, "")) {
308 if (new_plt_button_state) {
309 snprintf (new_title, sizeof (new_title), "%s", "New Playlist");
310 }
311 else {
312 snprintf (new_title, sizeof (new_title), "%s", "Quick Search");
313 }
314 }
315 else {
316 if (new_plt_button_state) {
317 snprintf (new_title, sizeof (new_title), "[%s]", search_string);
318 }
319 else {
320 snprintf (new_title, sizeof (new_title), "%s [%s]", "Quick Search", search_string);
321 }
322 }
323 deadbeef->plt_set_title (plt, new_title);
324 }
325 deadbeef->pl_unlock ();
326 }
327
328 static void
copy_selected_tracks(ddb_playlist_t * from,ddb_playlist_t * to)329 copy_selected_tracks (ddb_playlist_t *from, ddb_playlist_t *to)
330 {
331 if (!from || !to) {
332 return;
333 }
334 deadbeef->pl_lock ();
335 deadbeef->plt_set_curr (to);
336
337 int sel_count = deadbeef->plt_get_sel_count (deadbeef->plt_get_idx (from));
338 uint32_t *track_list = malloc ((sel_count) * sizeof (uint32_t));
339 if (track_list) {
340 int track_idx = 0;
341 int i = 0;
342 DB_playItem_t *it = deadbeef->plt_get_first (from, PL_MAIN);
343 for (; it; track_idx++) {
344 if (deadbeef->pl_is_selected (it)) {
345 track_list[i] = track_idx;
346 i++;
347 }
348 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
349 deadbeef->pl_item_unref (it);
350 it = next;
351 }
352 DB_playItem_t *after = deadbeef->plt_get_first (to, PL_MAIN);
353 deadbeef->plt_copy_items (to, PL_MAIN, from, after, track_list, sel_count);
354 if (after) {
355 deadbeef->pl_item_unref (after);
356 }
357 free (track_list);
358 }
359 deadbeef->pl_unlock ();
360 }
361
362 static void
on_add_quick_search_list()363 on_add_quick_search_list ()
364 {
365 deadbeef->pl_lock ();
366 int new_plt_idx = -1;
367 if (new_plt_button_state) {
368 if (added_plt == NULL) {
369 new_plt_idx = add_new_playlist ("Quick Search*");
370 added_plt = deadbeef->plt_get_for_idx (new_plt_idx);
371 }
372 else {
373 new_plt_idx = deadbeef->plt_get_idx (added_plt);
374 }
375 }
376 else {
377 new_plt_idx = get_quick_search_playlist ();
378 if (added_plt) {
379 deadbeef->plt_unref (added_plt);
380 added_plt = NULL;
381 }
382 }
383
384 ddb_playlist_t *plt_to = deadbeef->plt_get_for_idx (new_plt_idx);
385
386 if (plt_to) {
387 if (config_search_in != SEARCH_ALL_PLAYLISTS) {
388 ddb_playlist_t *plt_from = get_last_active_playlist ();
389 if (plt_from) {
390 if (is_quick_search_playlist (plt_from)) {
391 deadbeef->plt_unref (plt_from);
392 deadbeef->plt_unref (plt_to);
393 deadbeef->pl_unlock ();
394 return;
395 }
396 deadbeef->plt_set_scroll (plt_to, 0);
397 deadbeef->plt_clear (plt_to);
398 copy_selected_tracks (plt_from, plt_to);
399 deadbeef->plt_unref (plt_from);
400 }
401 }
402 else if (config_search_in == SEARCH_ALL_PLAYLISTS) {
403 deadbeef->plt_set_scroll (plt_to, 0);
404 deadbeef->plt_clear (plt_to);
405 int plt_count = deadbeef->plt_get_count ();
406 for (int i = 0; i < plt_count; i++) {
407 ddb_playlist_t *plt_from = deadbeef->plt_get_for_idx (i);
408 if (!plt_from) {
409 continue;
410 }
411 if (!is_quick_search_playlist (plt_from)) {
412 copy_selected_tracks (plt_from, plt_to);
413 }
414 deadbeef->plt_unref (plt_from);
415 }
416 }
417 if (config_append_search_string && config_search_in != SEARCH_INLINE) {
418 const gchar *text = gtk_entry_get_text (GTK_ENTRY (searchentry));
419 append_search_string_to_plt_title (plt_to, text);
420 }
421
422 deadbeef->plt_unref (plt_to);
423 }
424 deadbeef->pl_unlock ();
425
426 #if (DDB_API_LEVEL >= 8)
427 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
428 #else
429 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, 0, 0);
430 #endif
431 }
432
433 static void
on_searchentry_activate(GtkEntry * entry,gpointer user_data)434 on_searchentry_activate (GtkEntry *entry,
435 gpointer user_data)
436 {
437 deadbeef->pl_lock ();
438 ddb_playlist_t *plt = get_last_active_playlist ();
439 if (plt) {
440 DB_playItem_t *it = deadbeef->plt_get_first (plt, PL_MAIN);
441 while (it) {
442 if (deadbeef->pl_is_selected (it)) {
443 int idx = deadbeef->plt_get_item_idx(plt, it, PL_MAIN);
444 deadbeef->sendmessage (DB_EV_PLAY_NUM, 0, idx, 0);
445 break;
446 }
447 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
448 deadbeef->pl_item_unref (it);
449 it = next;
450 }
451 if (it) {
452 deadbeef->pl_item_unref (it);
453 }
454 deadbeef->plt_unref (plt);
455 }
456 deadbeef->pl_unlock ();
457 }
458
459 static void
on_searchentry_icon_press(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)460 on_searchentry_icon_press (GtkEntry *entry,
461 GtkEntryIconPosition icon_pos,
462 GdkEvent *event,
463 gpointer user_data)
464 {
465 w_quick_search_t *w = user_data;
466 if (icon_pos == GTK_ENTRY_ICON_PRIMARY) {
467 gtk_menu_popup (GTK_MENU (w->popup), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
468 }
469 else {
470 gtk_entry_set_text (entry, "");
471 }
472 }
473
474 static void
add_history_query_to_combo(gpointer user_data,const char * text,int prepend)475 add_history_query_to_combo (gpointer user_data, const char *text, int prepend)
476 {
477 if (!user_data || !text) {
478 return;
479 }
480 w_quick_search_t *w = user_data;
481
482 if (history_entries >= config_history_size) {
483 gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (w->combo), config_history_size);
484 }
485
486 if (prepend) {
487 gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (w->combo), text);
488 }
489 else {
490 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (w->combo), text);
491 }
492
493 if (!history_entries) {
494 gtk_widget_set_sensitive (GTK_WIDGET (w->clear_history), TRUE);
495 }
496 history_entries++;
497 }
498
499 static void
add_history_entry(gpointer user_data)500 add_history_entry (gpointer user_data)
501 {
502 if (!user_data) {
503 return;
504 }
505 w_quick_search_t *w = user_data;
506 const gchar *text = gtk_entry_get_text (GTK_ENTRY (searchentry));
507 if (text) {
508 if (!strcmp (text, "")) {
509 return;
510 }
511 if (w->prev_query) {
512 if (strcmp (w->prev_query, text)) {
513 free (w->prev_query);
514 w->prev_query = NULL;
515 w->prev_query = strdup (text);
516 add_history_query_to_combo (user_data, text, 1);
517 }
518 }
519 else {
520 w->prev_query = strdup (text);
521 add_history_query_to_combo (user_data, text, 1);
522 }
523 }
524 }
525
526 static void
searchentry_perform_autosearch()527 searchentry_perform_autosearch ()
528 {
529 switch (config_search_in) {
530 #if (DDB_API_LEVEL >= 8)
531 case SEARCH_INLINE:
532 deadbeef->sendmessage (DB_EV_FOCUS_SELECTION, 0, PL_MAIN, 0);
533 break;
534 #endif
535 case SEARCH_PLAYLIST:
536 case SEARCH_ALL_PLAYLISTS:
537 on_add_quick_search_list ();
538 break;
539 }
540 }
541
542 static gboolean
543 search_process (gpointer userdata);
544
545 static gboolean
on_searchentry_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)546 on_searchentry_key_press_event (GtkWidget *widget,
547 GdkEventKey *event,
548 gpointer user_data)
549 {
550 #if GTK_CHECK_VERSION(3,0,0)
551 if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
552 #else
553 if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
554 #endif
555 if (!config_autosearch) {
556 GtkEntry *entry = GTK_ENTRY (widget);
557 const gchar *text = gtk_entry_get_text (entry);
558 if (search_delay_timer) {
559 g_source_remove (search_delay_timer);
560 search_delay_timer = 0;
561 }
562 search_delay_timer = g_timeout_add (100, search_process, (void *)text);
563 }
564 else {
565 on_searchentry_activate (NULL, 0);
566 }
567 add_history_entry (user_data);
568 if (added_plt) {
569 deadbeef->plt_unref (added_plt);
570 added_plt = NULL;
571 }
572 return TRUE;
573 }
574 return FALSE;
575 }
576
577 static void
578 update_list ()
579 {
580 #if (DDB_API_LEVEL >= 8)
581 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_SELECTION, 0);
582 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_SEARCHRESULT, 0);
583 #else
584 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, 0, 0);
585 #endif
586 }
587
588 static gboolean
589 search_process (gpointer userdata) {
590 if (search_delay_timer) {
591 g_source_remove (search_delay_timer);
592 search_delay_timer = 0;
593 }
594 g_return_val_if_fail (userdata != NULL, FALSE);
595
596 const char *text = userdata;
597 deadbeef->pl_lock ();
598 if (config_search_in != SEARCH_ALL_PLAYLISTS) {
599 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
600 if (plt) {
601 if (is_quick_search_playlist (plt)) {
602 deadbeef->plt_unref (plt);
603 plt = get_last_active_playlist ();
604 }
605 else {
606 set_last_active_playlist (plt);
607 }
608 if (plt) {
609 deadbeef->plt_search_process (plt, text);
610 deadbeef->plt_unref (plt);
611 }
612 }
613 }
614 else {
615 ddb_playlist_t *plt_curr = deadbeef->plt_get_curr ();
616 if (plt_curr) {
617 set_last_active_playlist (plt_curr);
618 deadbeef->plt_unref (plt_curr);
619 }
620 int plt_count = deadbeef->plt_get_count ();
621 for (int i = 0; i < plt_count; i++) {
622 ddb_playlist_t *plt = deadbeef->plt_get_for_idx (i);
623 if (!plt) {
624 continue;
625 }
626 if (!is_quick_search_playlist (plt)) {
627 deadbeef->plt_deselect_all (plt);
628 deadbeef->plt_search_process (plt, text);
629 }
630 deadbeef->plt_unref (plt);
631 }
632 }
633 deadbeef->pl_unlock ();
634
635 update_list ();
636 searchentry_perform_autosearch ();
637 if (config_autosearch && !strcmp (text, "")){
638 ddb_playlist_t *plt = get_last_active_playlist ();
639 if (plt) {
640 deadbeef->plt_set_curr (plt);
641 deadbeef->plt_unref (plt);
642 }
643 }
644
645 return FALSE;
646 }
647
648 static void
649 on_searchentry_changed (GtkEditable *editable,
650 gpointer user_data)
651 {
652 if (config_autosearch) {
653 GtkEntry *entry = GTK_ENTRY (editable);
654 const gchar *text = gtk_entry_get_text (entry);
655 if (search_delay_timer) {
656 g_source_remove (search_delay_timer);
657 search_delay_timer = 0;
658 }
659 search_delay_timer = g_timeout_add (100, search_process, (void *)text);
660 }
661 }
662
663 static gboolean
664 on_searchentry_focus_out_event (GtkWidget *widget,
665 GdkEvent *event,
666 gpointer user_data)
667 {
668 if (added_plt) {
669 deadbeef->plt_unref (added_plt);
670 added_plt = NULL;
671 }
672 add_history_entry (user_data);
673 return FALSE;
674 }
675
676 static gboolean
677 on_searchentry_focus_in_event (GtkWidget *widget,
678 GdkEvent *event,
679 gpointer user_data)
680 {
681 on_searchentry_changed (GTK_EDITABLE (widget), user_data);
682 return FALSE;
683 }
684
685 static int initialized = 0;
686
687 static int
688 quick_search_on_action (DB_plugin_action_t *action, int ctx)
689 {
690 if (initialized && searchentry) {
691 gtk_widget_grab_focus (searchentry);
692 }
693 return 0;
694 }
695
696 static void
697 quick_search_set_placeholder_text ()
698 {
699 #if GTK_CHECK_VERSION(3,0,0)
700 switch (config_search_in) {
701 case SEARCH_INLINE:
702 gtk_entry_set_placeholder_text (GTK_ENTRY (searchentry), "Search in playlist (inline)...");
703 break;
704 case SEARCH_PLAYLIST:
705 gtk_entry_set_placeholder_text (GTK_ENTRY (searchentry), "Search in playlist...");
706 break;
707 case SEARCH_ALL_PLAYLISTS:
708 gtk_entry_set_placeholder_text (GTK_ENTRY (searchentry), "Search in all playlists...");
709 break;
710 default:
711 gtk_entry_set_placeholder_text (GTK_ENTRY (searchentry), "Search...");
712 break;
713 }
714 #endif
715 }
716
717 static void
718 on_search_playlist_inline_activate (GtkMenuItem *menuitem,
719 gpointer user_data)
720 {
721 deadbeef->conf_set_int (CONFSTR_SEARCH_IN, SEARCH_INLINE);
722 deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
723 config_search_in = SEARCH_INLINE;
724 quick_search_set_placeholder_text ();
725 }
726
727 static void
728 on_search_playlist_activate (GtkMenuItem *menuitem,
729 gpointer user_data)
730 {
731 deadbeef->conf_set_int (CONFSTR_SEARCH_IN, SEARCH_PLAYLIST);
732 deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
733 config_search_in = SEARCH_PLAYLIST;
734 quick_search_set_placeholder_text ();
735 }
736
737 static void
738 on_search_all_playlists_activate (GtkMenuItem *menuitem,
739 gpointer user_data)
740 {
741 deadbeef->conf_set_int (CONFSTR_SEARCH_IN, SEARCH_ALL_PLAYLISTS);
742 deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
743 config_search_in = SEARCH_ALL_PLAYLISTS;
744 quick_search_set_placeholder_text ();
745 }
746
747 static void
748 on_autosearch_activate (GtkMenuItem *menuitem,
749 gpointer user_data)
750 {
751 config_autosearch = config_autosearch ? FALSE : TRUE;
752 deadbeef->conf_set_int (CONFSTR_AUTOSEARCH, config_autosearch);
753 deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
754 }
755
756 static void
757 on_clear_history_activate (GtkMenuItem *menuitem,
758 gpointer user_data)
759 {
760 w_quick_search_t *w = user_data;
761 if (history_entries) {
762 for (int i = 0; i < history_entries; i++) {
763 gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (w->combo), 0);
764 }
765 history_entries = 0;
766 gtk_widget_set_sensitive (GTK_WIDGET (w->clear_history), FALSE);
767 }
768 }
769
770 static void
771 quick_search_create_popup_menu (gpointer user_data)
772 {
773 w_quick_search_t *w = user_data;
774 w->popup = gtk_menu_new ();
775 gtk_widget_show (w->popup);
776
777 GtkWidget *search_in = gtk_menu_item_new_with_mnemonic ("Search in");
778 gtk_container_add (GTK_CONTAINER (w->popup), search_in);
779 gtk_widget_show (search_in);
780
781 GtkWidget *search_in_menu = gtk_menu_new ();
782 gtk_menu_item_set_submenu (GTK_MENU_ITEM (search_in), search_in_menu);
783
784
785 GSList *search_in_group = NULL;
786 GtkWidget *search_playlist_inline = gtk_radio_menu_item_new_with_mnemonic (search_in_group, "Playlist (inline)");
787 search_in_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (search_playlist_inline));
788 gtk_widget_show (search_playlist_inline);
789 gtk_container_add (GTK_CONTAINER (search_in_menu), search_playlist_inline);
790 g_signal_connect ((gpointer) search_playlist_inline, "activate",
791 G_CALLBACK (on_search_playlist_inline_activate),
792 NULL);
793
794 GtkWidget *search_playlist = gtk_radio_menu_item_new_with_mnemonic (search_in_group, "Playlist");
795 search_in_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (search_playlist));
796 gtk_widget_show (search_playlist);
797 gtk_container_add (GTK_CONTAINER (search_in_menu), search_playlist);
798 g_signal_connect ((gpointer) search_playlist, "activate",
799 G_CALLBACK (on_search_playlist_activate),
800 NULL);
801
802 GtkWidget *search_all_playlists = gtk_radio_menu_item_new_with_mnemonic (search_in_group, "All Playlists");
803 search_in_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (search_all_playlists));
804 gtk_widget_show (search_all_playlists);
805 gtk_container_add (GTK_CONTAINER (search_in_menu), search_all_playlists);
806 g_signal_connect ((gpointer) search_all_playlists, "activate",
807 G_CALLBACK (on_search_all_playlists_activate),
808 NULL);
809
810 GtkWidget *autosearch = gtk_check_menu_item_new_with_mnemonic ("Autosearch");
811 gtk_widget_show (autosearch);
812 gtk_container_add (GTK_CONTAINER (w->popup), autosearch);
813 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (autosearch), config_autosearch);
814 g_signal_connect ((gpointer) autosearch, "activate",
815 G_CALLBACK (on_autosearch_activate),
816 NULL);
817
818 GtkWidget *separator = gtk_separator_menu_item_new ();
819 gtk_widget_show (separator);
820 gtk_container_add (GTK_CONTAINER (w->popup), separator);
821
822 w->clear_history = gtk_menu_item_new_with_mnemonic ("Clear history");
823 gtk_widget_show (w->clear_history);
824 gtk_container_add (GTK_CONTAINER (w->popup), w->clear_history);
825 gtk_widget_set_sensitive (GTK_WIDGET (w->clear_history), history_entries);
826 g_signal_connect ((gpointer) w->clear_history, "activate",
827 G_CALLBACK (on_clear_history_activate),
828 user_data);
829
830 if (config_search_in == SEARCH_INLINE) {
831 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (search_playlist_inline), TRUE);
832 }
833 else if (config_search_in == SEARCH_PLAYLIST) {
834 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (search_playlist), TRUE);
835 }
836 else if (config_search_in == SEARCH_ALL_PLAYLISTS) {
837 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (search_all_playlists), TRUE);
838 }
839 }
840
841 static int
842 quick_search_message (ddb_gtkui_widget_t *widget, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2)
843 {
844 switch (id) {
845 case DB_EV_CONFIGCHANGED:
846 config_search_in = deadbeef->conf_get_int (CONFSTR_SEARCH_IN, FALSE);
847 config_autosearch = deadbeef->conf_get_int (CONFSTR_AUTOSEARCH, TRUE);
848 config_append_search_string = deadbeef->conf_get_int (CONFSTR_APPEND_SEARCH_STRING, FALSE);
849
850 if (!config_append_search_string) {
851 set_default_quick_search_playlist_title ();
852 }
853 break;
854 }
855 return 0;
856 }
857
858
859 #if GTK_CHECK_VERSION(3,0,0)
860 static DB_plugin_action_t
861 quick_search_action_gtk3 = {
862 .title = "Quick search (GTK3)",
863 .name = "quick_search_gtk3",
864 .flags = DB_ACTION_COMMON,
865 .callback2 = quick_search_on_action,
866 .next = NULL
867 };
868 #else
869 static DB_plugin_action_t
870 quick_search_action = {
871 .title = "Quick search",
872 .name = "quick_search",
873 .flags = DB_ACTION_COMMON,
874 .callback2 = quick_search_on_action,
875 .next = NULL
876 };
877 #endif
878
879 static DB_plugin_action_t *
880 quick_search_get_actions (DB_playItem_t *it)
881 {
882 #if GTK_CHECK_VERSION(3,0,0)
883 return &quick_search_action_gtk3;
884 #else
885 return &quick_search_action;
886 #endif
887 }
888
889 static void
890 quick_search_init (ddb_gtkui_widget_t *ww) {
891 w_quick_search_t *w = (w_quick_search_t *)ww;
892
893 cache_path_size = make_cache_dir (cache_path, sizeof (cache_path));
894
895 GtkWidget *hbox = gtk_hbox_new (FALSE, 3);
896 gtk_widget_show (hbox);
897 gtk_container_add (GTK_CONTAINER (w->base.widget), hbox);
898
899 searchentry = gtk_entry_new ();
900 #if GTK_CHECK_VERSION(3,0,0)
901 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_PRIMARY, "edit-find-symbolic");
902 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
903 #else
904 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_PRIMARY, "edit-find");
905 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_SECONDARY, "edit-clear");
906 #endif
907 gtk_entry_set_invisible_char (GTK_ENTRY (searchentry), 8226);
908 gtk_entry_set_activates_default (GTK_ENTRY (searchentry), TRUE);
909 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_PRIMARY, "Preferences");
910 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (searchentry), GTK_ENTRY_ICON_SECONDARY, "Clear the search text");
911 gtk_widget_show (searchentry);
912
913 w->combo = gtk_combo_box_text_new_with_entry ();
914 gtk_container_add (GTK_CONTAINER (hbox), w->combo);
915 gtk_container_remove (GTK_CONTAINER (w->combo), gtk_bin_get_child (GTK_BIN (w->combo)));
916 gtk_container_add (GTK_CONTAINER (w->combo), searchentry);
917 gtk_widget_show (w->combo);
918
919 GtkEntryCompletion *completion = gtk_entry_completion_new ();
920 gtk_entry_set_completion (GTK_ENTRY (searchentry), completion);
921 g_object_unref (completion);
922 gtk_entry_completion_set_model (completion, gtk_combo_box_get_model (GTK_COMBO_BOX (w->combo)));
923 gtk_entry_completion_set_text_column (completion, 0);
924
925 g_signal_connect ((gpointer) searchentry, "changed",
926 G_CALLBACK (on_searchentry_changed),
927 NULL);
928 g_signal_connect ((gpointer) searchentry, "key_press_event",
929 G_CALLBACK (on_searchentry_key_press_event),
930 w);
931 g_signal_connect ((gpointer) searchentry, "focus_in_event",
932 G_CALLBACK (on_searchentry_focus_in_event),
933 NULL);
934 g_signal_connect ((gpointer) searchentry, "focus_out_event",
935 G_CALLBACK (on_searchentry_focus_out_event),
936 w);
937 g_signal_connect ((gpointer) searchentry, "icon_release",
938 G_CALLBACK (on_searchentry_icon_press),
939 w);
940
941 config_search_in = deadbeef->conf_get_int (CONFSTR_SEARCH_IN, FALSE);
942 config_autosearch = deadbeef->conf_get_int (CONFSTR_AUTOSEARCH, TRUE);
943 config_append_search_string = deadbeef->conf_get_int (CONFSTR_APPEND_SEARCH_STRING, FALSE);
944 quick_search_set_placeholder_text ();
945 quick_search_create_popup_menu (w);
946 load_history_entries (w);
947
948 initialized = 1;
949 }
950
951 static void
952 quick_search_destroy (ddb_gtkui_widget_t *w) {
953 w_quick_search_t *ww = (w_quick_search_t *)w;
954 if (last_active_plt) {
955 deadbeef->plt_unref (last_active_plt);
956 last_active_plt = NULL;
957 }
958 if (ww->prev_query) {
959 free (ww->prev_query);
960 ww->prev_query = NULL;
961 }
962 if (search_delay_timer) {
963 g_source_remove (search_delay_timer);
964 search_delay_timer = 0;
965 }
966 }
967
968 static void
969 quick_search_save (struct ddb_gtkui_widget_s *w, char *s, int sz)
970 {
971 save_history_entries (w);
972 }
973
974 static ddb_gtkui_widget_t *
975 w_quick_search_create (void) {
976 w_quick_search_t *w = malloc (sizeof (w_quick_search_t));
977 memset (w, 0, sizeof (w_quick_search_t));
978
979 w->base.widget = gtk_event_box_new ();
980 w->base.destroy = quick_search_destroy;
981 w->base.init = quick_search_init;
982 w->base.save = quick_search_save;
983 w->base.message = quick_search_message;
984 gtkui_plugin->w_override_signals (w->base.widget, w);
985
986 return (ddb_gtkui_widget_t *)w;
987 }
988
989 static int
990 quick_search_connect (void)
991 {
992 gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID);
993 if (gtkui_plugin) {
994 //trace("using '%s' plugin %d.%d\n", DDB_GTKUI_PLUGIN_ID, gtkui_plugin->gui.plugin.version_major, gtkui_plugin->gui.plugin.version_minor );
995 if (gtkui_plugin->gui.plugin.version_major == 2) {
996 //printf ("fb api2\n");
997 // 0.6+, use the new widget API
998 gtkui_plugin->w_reg_widget ("Quick search", DDB_WF_SINGLE_INSTANCE, w_quick_search_create, "quick_search", NULL);
999 return 0;
1000 }
1001 }
1002 return -1;
1003 }
1004
1005 static const char settings_dlg[] =
1006 "property \"Append search string to playlist name \" checkbox " CONFSTR_APPEND_SEARCH_STRING " 0 ;\n"
1007 "property \"History size: \" spinbtn[0,20,1] " CONFSTR_HISTORY_SIZE " 10 ;\n"
1008 ;
1009
1010 static int
1011 quick_search_disconnect (void)
1012 {
1013 gtkui_plugin = NULL;
1014 return 0;
1015 }
1016
1017 // define plugin interface
1018 static DB_misc_t plugin = {
1019 .plugin.api_vmajor = 1,
1020 .plugin.api_vminor = 5,
1021 .plugin.version_major = 0,
1022 .plugin.version_minor = 1,
1023 #if GTK_CHECK_VERSION(3,0,0)
1024 .plugin.id = "quick_search-gtk3",
1025 #else
1026 .plugin.id = "quick_search",
1027 #endif
1028 .plugin.type = DB_PLUGIN_MISC,
1029 .plugin.name = "Quick search",
1030 .plugin.descr = "A widget to perform a quick search",
1031 .plugin.copyright =
1032 "Copyright (C) 2015 Christian Boxdörfer <christian.boxdoerfer@posteo.de>\n"
1033 "\n"
1034 "This program is free software; you can redistribute it and/or\n"
1035 "modify it under the terms of the GNU General Public License\n"
1036 "as published by the Free Software Foundation; either version 2\n"
1037 "of the License, or (at your option) any later version.\n"
1038 "\n"
1039 "This program is distributed in the hope that it will be useful,\n"
1040 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1041 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
1042 "GNU General Public License for more details.\n"
1043 "\n"
1044 "You should have received a copy of the GNU General Public License\n"
1045 "along with this program; if not, write to the Free Software\n"
1046 "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
1047 ,
1048 .plugin.website = "http://www.github.com/cboxdoerfer/ddb_quick_search",
1049 .plugin.connect = quick_search_connect,
1050 .plugin.disconnect = quick_search_disconnect,
1051 .plugin.get_actions = quick_search_get_actions,
1052 .plugin.configdialog = settings_dlg,
1053 };
1054
1055 #if !GTK_CHECK_VERSION(3,0,0)
1056 DB_plugin_t *
1057 ddb_misc_quick_search_GTK2_load (DB_functions_t *ddb) {
1058 deadbeef = ddb;
1059 return &plugin.plugin;
1060 }
1061 #else
1062 DB_plugin_t *
1063 ddb_misc_quick_search_GTK3_load (DB_functions_t *ddb) {
1064 deadbeef = ddb;
1065 return &plugin.plugin;
1066 }
1067 #endif
1068