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