1 /*
2  * Playlist Manager Plugin for Audacious
3  * Copyright 2006-2014 Giacomo Lozito, John Lindgren, and Thomas Lange
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include <libaudcore/audstrings.h>
21 #include <libaudcore/hook.h>
22 #include <libaudcore/i18n.h>
23 #include <libaudcore/playlist.h>
24 #include <libaudcore/plugin.h>
25 #include <libaudcore/runtime.h>
26 
27 #include <libaudgui/libaudgui.h>
28 #include <libaudgui/libaudgui-gtk.h>
29 #include <libaudgui/list.h>
30 
31 #include <string.h>
32 
33 class PlaylistManager : public GeneralPlugin
34 {
35 public:
36     static constexpr PluginInfo info = {
37         N_("Playlist Manager"),
38         PACKAGE,
39         nullptr, // about
40         nullptr, // prefs
41         PluginGLibOnly
42     };
43 
PlaylistManager()44     constexpr PlaylistManager () : GeneralPlugin (info, false) {}
45 
46     void * get_gtk_widget ();
47     int take_message (const char * code, const void * data, int size);
48 };
49 
50 EXPORT PlaylistManager aud_plugin_instance;
51 
52 static GtkWidget * focus_widget;
53 
54 static void activate_row (void * user, int row);
55 
rename_cb(void * unused)56 static void rename_cb (void * unused)
57 {
58     audgui_show_playlist_rename (Playlist::active_playlist ());
59 }
60 
delete_cb(void * unused)61 static void delete_cb (void * unused)
62 {
63     audgui_confirm_playlist_delete (Playlist::active_playlist ());
64 }
65 
get_value(void * user,int row,int column,GValue * value)66 static void get_value (void * user, int row, int column, GValue * value)
67 {
68     auto list = Playlist::by_index (row);
69 
70     switch (column)
71     {
72     case 0:
73         g_value_set_string (value, list.get_title ());
74         break;
75 
76     case 1:
77         g_value_set_int (value, list.n_entries ());
78         break;
79     }
80 }
81 
get_selected(void * user,int row)82 static bool get_selected (void * user, int row)
83 {
84     return (row == Playlist::active_playlist ().index ());
85 }
86 
set_selected(void * user,int row,bool selected)87 static void set_selected (void * user, int row, bool selected)
88 {
89     if (selected)
90         Playlist::by_index (row).activate ();
91 }
92 
select_all(void * user,bool selected)93 static void select_all (void * user, bool selected)
94 {
95 }
96 
activate_row(void * user,int row)97 static void activate_row (void * user, int row)
98 {
99     auto playlist = Playlist::by_index (row);
100     playlist.activate ();
101     playlist.start_playback ();
102 }
103 
shift_rows(void * user,int row,int before)104 static void shift_rows (void * user, int row, int before)
105 {
106     if (before < row)
107         Playlist::reorder_playlists (row, before, 1);
108     else if (before - 1 > row)
109         Playlist::reorder_playlists (row, before - 1, 1);
110 }
111 
112 static const AudguiListCallbacks callbacks = {
113     get_value,
114     get_selected,
115     set_selected,
116     select_all,
117     activate_row,
118     nullptr,  // right_click
119     shift_rows
120 };
121 
search_cb(GtkTreeModel * model,int column,const char * key,GtkTreeIter * iter,void * user)122 static gboolean search_cb (GtkTreeModel * model, int column, const char * key,
123  GtkTreeIter * iter, void * user)
124 {
125     GtkTreePath * path = gtk_tree_model_get_path (model, iter);
126     g_return_val_if_fail (path, true);
127     int row = gtk_tree_path_get_indices (path)[0];
128     gtk_tree_path_free (path);
129 
130     String title = Playlist::by_index (row).get_title ();
131     g_return_val_if_fail (title, true);
132 
133     Index<String> keys = str_list_to_index (key, " ");
134 
135     if (! keys.len ())
136         return true;  /* not matched */
137 
138     for (const String & key : keys)
139     {
140         if (! strstr_nocase_utf8 (title, key))
141             return true;  /* not matched */
142     }
143 
144     return false;  /* matched */
145 }
146 
update_hook(void * data,void * list_)147 static void update_hook (void * data, void * list_)
148 {
149     auto level = aud::from_ptr<Playlist::UpdateLevel> (data);
150     GtkWidget * list = (GtkWidget *) list_;
151     int rows = Playlist::n_playlists ();
152 
153     if (level == Playlist::Structure)
154     {
155         int old_rows = audgui_list_row_count (list);
156 
157         if (rows < old_rows)
158             audgui_list_delete_rows (list, rows, old_rows - rows);
159         else if (rows > old_rows)
160             audgui_list_insert_rows (list, old_rows, rows - old_rows);
161 
162         audgui_list_set_focus (list, Playlist::active_playlist ().index ());
163         audgui_list_set_highlight (list, Playlist::playing_playlist ().index ());
164         audgui_list_update_selection (list, 0, rows);
165     }
166 
167     if (level >= Playlist::Metadata)
168         audgui_list_update_rows (list, 0, rows);
169 }
170 
activate_hook(void * data,void * list_)171 static void activate_hook (void * data, void * list_)
172 {
173     GtkWidget * list = (GtkWidget *) list_;
174     audgui_list_set_focus (list, Playlist::active_playlist ().index ());
175     audgui_list_update_selection (list, 0, Playlist::n_playlists ());
176 }
177 
position_hook(void * data,void * list_)178 static void position_hook (void * data, void * list_)
179 {
180     GtkWidget * list = (GtkWidget *) list_;
181     audgui_list_set_highlight (list, Playlist::playing_playlist ().index ());
182 }
183 
destroy_cb(GtkWidget * window)184 static void destroy_cb (GtkWidget * window)
185 {
186     hook_dissociate ("playlist update", update_hook);
187     hook_dissociate ("playlist activate", activate_hook);
188     hook_dissociate ("playlist set playing", position_hook);
189 
190     focus_widget = nullptr;
191 }
192 
get_gtk_widget()193 void * PlaylistManager::get_gtk_widget ()
194 {
195     GtkWidget * playman_vbox = gtk_vbox_new (false, 6);
196 
197     /* ListView */
198     GtkWidget * playman_pl_lv = audgui_list_new (& callbacks, nullptr, Playlist::n_playlists ());
199     audgui_list_add_column (playman_pl_lv, _("Title"), 0, G_TYPE_STRING, -1);
200     audgui_list_add_column (playman_pl_lv, _("Entries"), 1, G_TYPE_INT, 7);
201     audgui_list_set_highlight (playman_pl_lv, Playlist::playing_playlist ().index ());
202     gtk_tree_view_set_search_equal_func ((GtkTreeView *) playman_pl_lv,
203      search_cb, nullptr, nullptr);
204     hook_associate ("playlist update", update_hook, playman_pl_lv);
205     hook_associate ("playlist activate", activate_hook, playman_pl_lv);
206     hook_associate ("playlist set playing", position_hook, playman_pl_lv);
207 
208     GtkWidget * playman_pl_lv_sw = gtk_scrolled_window_new (nullptr, nullptr);
209     gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) playman_pl_lv_sw,
210      GTK_SHADOW_IN);
211     gtk_scrolled_window_set_policy ((GtkScrolledWindow *) playman_pl_lv_sw,
212      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
213     gtk_container_add ((GtkContainer *) playman_pl_lv_sw, playman_pl_lv);
214     gtk_box_pack_start ((GtkBox *) playman_vbox, playman_pl_lv_sw, true, true, 0);
215 
216     /* ButtonBox */
217     GtkWidget * playman_button_hbox = gtk_hbox_new (false, 6);
218     GtkWidget * new_button = audgui_button_new (_("_New"), "document-new",
219      [] (void *) { Playlist::new_playlist (); }, nullptr);
220     GtkWidget * delete_button = audgui_button_new (_("_Remove"), "edit-delete", delete_cb, nullptr);
221     GtkWidget * rename_button = audgui_button_new (_("Ren_ame"), "insert-text", rename_cb, nullptr);
222 
223     gtk_box_pack_start ((GtkBox *) playman_button_hbox, new_button, false, false, 0);
224     gtk_box_pack_start ((GtkBox *) playman_button_hbox, delete_button, false, false, 0);
225     gtk_box_pack_end ((GtkBox *) playman_button_hbox, rename_button, false, false, 0);
226     gtk_box_pack_start ((GtkBox *) playman_vbox, playman_button_hbox, false, false, 0);
227 
228     focus_widget = playman_pl_lv;
229 
230     g_signal_connect (playman_vbox, "destroy", (GCallback) destroy_cb, nullptr);
231 
232     return playman_vbox;
233 }
234 
take_message(const char * code,const void * data,int size)235 int PlaylistManager::take_message (const char * code, const void * data, int size)
236 {
237     if (! strcmp (code, "grab focus"))
238     {
239         if (focus_widget)
240             gtk_widget_grab_focus (focus_widget);
241 
242         return 0;
243     }
244 
245     return -1;
246 }
247