1 /*
2     DeaDBeeF -- the music player
3     Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4 
5     This software is provided 'as-is', without any express or implied
6     warranty.  In no event will the authors be held liable for any damages
7     arising from the use of this software.
8 
9     Permission is granted to anyone to use this software for any purpose,
10     including commercial applications, and to alter it and redistribute it
11     freely, subject to the following restrictions:
12 
13     1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17 
18     2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20 
21     3. This notice may not be removed or altered from any source distribution.
22 */
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 
28 #include "gtkui.h"
29 #include "../../deadbeef.h"
30 #include "support.h"
31 #include "actions.h"
32 
33 #define trace(...) { fprintf(stderr, __VA_ARGS__); }
34 //#define trace(fmt,...)
35 
36 #define GLADE_HOOKUP_OBJECT(component,widget,name) \
37   g_object_set_data_full (G_OBJECT (component), name, \
38     g_object_ref(G_OBJECT(widget)), (GDestroyNotify) g_object_unref)
39 
40 static gboolean
menu_action_cb(void * ctx)41 menu_action_cb (void *ctx) {
42     DB_plugin_action_t *action = ctx;
43     if (action->callback) {
44         gtkui_exec_action_14 (action, -1);
45     }
46     else if (action->callback2) {
47         action->callback2 (action, DDB_ACTION_CTX_MAIN);
48     }
49     return FALSE;
50 }
51 
52 static void
on_actionitem_activate(GtkMenuItem * menuitem,DB_plugin_action_t * action)53 on_actionitem_activate (GtkMenuItem     *menuitem,
54                            DB_plugin_action_t *action)
55 {
56     // these actions are always in the MAIN context, or they are coming from new
57     // plugins, so we don't have to care about the user data for <=1.4 plugins.
58     // aren't we?..
59     gdk_threads_add_idle (menu_action_cb, action);
60 }
61 
62 void
remove_actions(GtkWidget * widget,void * data)63 remove_actions (GtkWidget *widget, void *data) {
64     const char *name = g_object_get_data (G_OBJECT (widget), "plugaction");
65     if (name) {
66         gtk_container_remove (GTK_CONTAINER (data), widget);
67     }
68     if (GTK_IS_MENU_ITEM (widget)) {
69         GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
70         if (menu) {
71             gtk_container_foreach (GTK_CONTAINER (menu), remove_actions, menu);
72             // if menu is empty -- remove parent menu item
73             GList *lst = gtk_container_get_children (GTK_CONTAINER (menu));
74             if (lst) {
75                 g_list_free (lst);
76             }
77             else {
78                 gtk_container_remove (data, widget);
79             }
80         }
81     }
82 }
83 
84 void
add_mainmenu_actions(void)85 add_mainmenu_actions (void)
86 {
87     GtkWidget *menubar = lookup_widget (mainwin, "menubar");
88     // remove all plugaction_*** menu items and empty submenus
89     gtk_container_foreach (GTK_CONTAINER (menubar), remove_actions, menubar);
90 
91     // add new
92     DB_plugin_t **plugins = deadbeef->plug_get_list();
93     int i;
94 
95     for (i = 0; plugins[i]; i++)
96     {
97         if (!plugins[i]->get_actions)
98             continue;
99 
100         DB_plugin_action_t *actions = plugins[i]->get_actions (NULL);
101         DB_plugin_action_t *action;
102 
103         for (action = actions; action; action = action->next)
104         {
105             char *tmp = NULL;
106 
107             int has_addmenu = (action->flags & DB_ACTION_COMMON) && ((action->flags & DB_ACTION_ADD_MENU) || (action->callback));
108 
109             if (!has_addmenu)
110                 continue;
111 
112             // 1st check if we have slashes
113             const char *slash = action->title;
114             while (NULL != (slash = strchr (slash, '/'))) {
115                 if (slash && slash > action->title && *(slash-1) == '\\') {
116                     slash++;
117                     continue;
118                 }
119                 break;
120             }
121             if (!slash) {
122                 continue;
123             }
124 
125             char *ptr = tmp = strdup (action->title);
126 
127             char *prev_title = NULL;
128 
129             GtkWidget *current = menubar;
130             GtkWidget *previous;
131 
132             while (1)
133             {
134                 // find unescaped forward slash
135                 char *slash = strchr (ptr, '/');
136                 if (slash && slash > ptr && *(slash-1) == '\\') {
137                     ptr = slash + 1;
138                     continue;
139                 }
140 
141                 if (!slash)
142                 {
143                     GtkWidget *actionitem;
144                     actionitem = gtk_image_menu_item_new_with_mnemonic (_(ptr));
145                     gtk_widget_show (actionitem);
146 
147                     /* Here we have special cases for different submenus */
148                     if (0 == strcmp ("File", prev_title))
149                         gtk_menu_shell_insert (GTK_MENU_SHELL (current), actionitem, 5);
150                     else if (0 == strcmp ("Edit", prev_title))
151                         gtk_menu_shell_insert (GTK_MENU_SHELL (current), actionitem, 7);
152                     else {
153                         gtk_container_add (GTK_CONTAINER (current), actionitem);
154                     }
155 
156                     g_signal_connect ((gpointer) actionitem, "activate",
157                         G_CALLBACK (on_actionitem_activate),
158                         action);
159                     g_object_set_data_full (G_OBJECT (actionitem), "plugaction", strdup (action->name), free);
160                     break;
161                 }
162                 *slash = 0;
163                 char menuname [1024];
164 
165                 snprintf (menuname, sizeof (menuname), "%s_menu", ptr);
166 
167                 previous = current;
168                 current = (GtkWidget*) g_object_get_data (G_OBJECT (mainwin), menuname);
169                 if (!current)
170                 {
171                     GtkWidget *newitem;
172 
173                     newitem = gtk_menu_item_new_with_mnemonic (ptr);
174                     gtk_widget_show (newitem);
175 
176                     //If we add new submenu in main bar, add it before 'Help'
177                     if (NULL == prev_title)
178                         gtk_menu_shell_insert (GTK_MENU_SHELL (previous), newitem, 4);
179                     else
180                         gtk_container_add (GTK_CONTAINER (previous), newitem);
181 
182                     current = gtk_menu_new ();
183                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (newitem), current);
184                     GLADE_HOOKUP_OBJECT (mainwin, current, menuname);
185                 }
186                 prev_title = ptr;
187                 ptr = slash + 1;
188             }
189             if (tmp) {
190                 free (tmp);
191             }
192         }
193     }
194 }
195 
196 void
gtkui_exec_action_14(DB_plugin_action_t * action,int cursor)197 gtkui_exec_action_14 (DB_plugin_action_t *action, int cursor) {
198     // Plugin can handle all tracks by itself
199     if (action->flags & DB_ACTION_CAN_MULTIPLE_TRACKS)
200     {
201         action->callback (action, NULL);
202         return;
203     }
204 
205     // For single-track actions just invoke it with first selected track
206     if (!(action->flags & DB_ACTION_MULTIPLE_TRACKS))
207     {
208         if (cursor == -1) {
209             cursor = deadbeef->pl_get_cursor (PL_MAIN);
210         }
211         if (cursor == -1)
212         {
213             return;
214         }
215         DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (cursor, PL_MAIN);
216         action->callback (action, it);
217         deadbeef->pl_item_unref (it);
218         return;
219     }
220 
221     //We end up here if plugin won't traverse tracks and we have to do it ourselves
222     DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
223     while (it) {
224         if (deadbeef->pl_is_selected (it)) {
225             action->callback (action, it);
226         }
227         DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
228         deadbeef->pl_item_unref (it);
229         it = next;
230     }
231 }
232