1 /*************************************************************************/
2 /* Copyright (C) 2007-2009 sujith <m.sujith@gmail.com>                   */
3 /* Copyright (C) 2009-2013 matias <mati86dl@gmail.com>                   */
4 /*                                                                       */
5 /* This program is free software: you can redistribute it and/or modify  */
6 /* it under the terms of the GNU General Public License as published by  */
7 /* the Free Software Foundation, either version 3 of the License, or     */
8 /* (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, see <http://www.gnu.org/licenses/>. */
17 /*************************************************************************/
18 
19 #if HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include "pragha-statusicon.h"
24 
25 #if defined(GETTEXT_PACKAGE)
26 #include <glib/gi18n-lib.h>
27 #else
28 #include <glib/gi18n.h>
29 #endif
30 
31 #include "pragha-playback.h"
32 #include "pragha-menubar.h"
33 #include "pragha-window.h"
34 #include "pragha.h"
35 
36 struct _PraghaStatusIcon {
37 	GtkStatusIcon __parent__;
38 
39 	PraghaApplication *pragha;
40 
41 	GtkUIManager   *ui_manager;
42 };
43 
44 G_DEFINE_TYPE(PraghaStatusIcon, pragha_status_icon, GTK_TYPE_STATUS_ICON)
45 
46 static void systray_about_action        (GtkAction *action, PraghaStatusIcon *status_icon);
47 static void systray_open_file_action    (GtkAction *action, PraghaStatusIcon *status_icon);
48 static void systray_add_location_action (GtkAction *action, PraghaStatusIcon *status_icon);
49 static void systray_play_pause_action   (GtkAction *action, PraghaStatusIcon *status_icon);
50 static void systray_stop_action         (GtkAction *action, PraghaStatusIcon *status_icon);
51 static void systray_prev_action         (GtkAction *action, PraghaStatusIcon *status_icon);
52 static void systray_next_action         (GtkAction *action, PraghaStatusIcon *status_icon);
53 static void systray_edit_action         (GtkAction *action, PraghaStatusIcon *status_icon);
54 static void systray_quit                (GtkAction *action, PraghaStatusIcon *status_icon);
55 
56 static const gchar *systray_menu_xml =
57 	"<ui>						\
58 	<popup>						\
59 		<menuitem action=\"About\"/>		\
60 		<separator/>				\
61 		<menuitem action=\"Add files\"/> \
62 		<menuitem action=\"Add location\"/>	\
63 		<placeholder name=\"pragha-append-music-placeholder\"/>	\
64 		<separator/>				\
65 		<menuitem action=\"Prev\"/>		\
66 		<menuitem action=\"Play_Pause\"/>	\
67 		<menuitem action=\"Stop\"/>		\
68 		<menuitem action=\"Next\"/>		\
69 		<separator/>				\
70 		<menuitem action=\"Edit tags\"/>	\
71 		<separator/>				\
72 		<menuitem action=\"Quit\"/>		\
73 	</popup>					\
74 	</ui>";
75 
76 static const GtkActionEntry systray_menu_aentries[] = {
77 	{"About", "help-about", N_("About"),
78 	 "", NULL, G_CALLBACK(systray_about_action)},
79 	{"Add files", "document-open", N_("_Add files"),
80 	 "", NULL, G_CALLBACK(systray_open_file_action)},
81 	{"Add location", "network-workgroup", N_("Add _location"),
82 	 "", "Add a no local stream", G_CALLBACK(systray_add_location_action)},
83 	{"Prev", "media-skip-backward", N_("Previous track"),
84 	 "", "Previous track", G_CALLBACK(systray_prev_action)},
85 	{"Play_Pause", "media-playback-start", N_("Play / Pause"),
86 	 "", "Play / Pause", G_CALLBACK(systray_play_pause_action)},
87 	{"Stop", "media-playback-stop", N_("Stop"),
88 	 "", "Stop", G_CALLBACK(systray_stop_action)},
89 	{"Next", "media-skip-forward", N_("Next track"),
90 	 "", "Next track", G_CALLBACK(systray_next_action)},
91 	{"Edit tags", NULL, N_("Edit track information"),
92 	 "", "Edit information of current track", G_CALLBACK(systray_edit_action)},
93 	{"Quit", "application-exit", N_("_Quit"),
94 	 "", "Quit", G_CALLBACK(systray_quit)}
95 };
96 
97 gint
pragha_systray_append_plugin_action(PraghaStatusIcon * status_icon,GtkActionGroup * action_group,const gchar * menu_xml)98 pragha_systray_append_plugin_action (PraghaStatusIcon *status_icon,
99                                      GtkActionGroup   *action_group,
100                                      const gchar      *menu_xml)
101 {
102 	GError *error = NULL;
103 	gint merge_id;
104 
105 	gtk_ui_manager_insert_action_group (status_icon->ui_manager, action_group, -1);
106 
107 	merge_id = gtk_ui_manager_add_ui_from_string (status_icon->ui_manager,
108 	                                              menu_xml,
109 	                                              -1,
110 	                                              &error);
111 
112 	if (error) {
113 		g_warning ("Adding plugin to menubar: %s", error->message);
114 		g_error_free (error);
115 	}
116 
117 	return merge_id;
118 }
119 
120 void
pragha_systray_remove_plugin_action(PraghaStatusIcon * status_icon,GtkActionGroup * action_group,gint merge_id)121 pragha_systray_remove_plugin_action (PraghaStatusIcon *status_icon,
122                                      GtkActionGroup   *action_group,
123                                      gint              merge_id)
124 {
125 	gtk_ui_manager_remove_ui (status_icon->ui_manager, merge_id);
126 	gtk_ui_manager_remove_action_group (status_icon->ui_manager, action_group);
127 	g_object_unref (action_group);
128 }
129 
130 /*
131  * Status Icon
132  */
133 
134 static gboolean
status_icon_clicked(GtkWidget * widget,GdkEventButton * event,PraghaStatusIcon * status_icon)135 status_icon_clicked (GtkWidget *widget, GdkEventButton *event, PraghaStatusIcon *status_icon)
136 {
137 	GtkWidget *popup_menu;
138 
139 	switch (event->button)
140 	{
141 		case 1:
142 			pragha_window_toggle_state (status_icon->pragha, FALSE);
143 			break;
144 		case 2:
145 			pragha_playback_play_pause_resume (status_icon->pragha);
146 			break;
147 		case 3:
148 			popup_menu = gtk_ui_manager_get_widget(status_icon->ui_manager, "/popup");
149 			gtk_menu_popup (GTK_MENU(popup_menu), NULL, NULL, NULL, NULL,
150 			                event->button, gtk_get_current_event_time ());
151 		default: break;
152 	}
153 
154 	return TRUE;
155 }
156 
157 static gboolean
status_get_tooltip_cb(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,PraghaStatusIcon * status_icon)158 status_get_tooltip_cb (GtkWidget        *widget,
159                        gint              x,
160                        gint              y,
161                        gboolean          keyboard_mode,
162                        GtkTooltip       *tooltip,
163                        PraghaStatusIcon *status_icon)
164 {
165 	PraghaBackend *backend;
166 	PraghaToolbar *toolbar;
167 	PraghaMusicobject *mobj;
168 	gchar *markup_text;
169 
170 	toolbar = pragha_application_get_toolbar (status_icon->pragha);
171 
172 	backend = pragha_application_get_backend (status_icon->pragha);
173 	if (pragha_backend_get_state (backend) == ST_STOPPED)
174 		markup_text = g_strdup_printf("%s", _("<b>Not playing</b>"));
175 	else {
176 		mobj = pragha_backend_get_musicobject (backend);
177 
178 		markup_text = g_markup_printf_escaped ("<b>%s</b>: %s\n<b>%s</b>: %s\n<b>%s</b>: %s\n<b>%s</b>: %s / %s",
179 		                                       _("Title"), pragha_musicobject_get_title (mobj),
180 		                                       _("Artist"), pragha_musicobject_get_artist (mobj),
181 		                                       _("Album"), pragha_musicobject_get_album (mobj),
182 		                                       _("Length"), pragha_toolbar_get_progress_text (toolbar),
183 		                                       pragha_toolbar_get_length_text (toolbar));
184 	}
185 
186 	gtk_tooltip_set_markup (tooltip, markup_text);
187 	gtk_tooltip_set_icon (tooltip,
188 		pragha_album_art_get_pixbuf(pragha_toolbar_get_album_art(toolbar)));
189 
190 	g_free(markup_text);
191 
192 	return TRUE;
193 }
194 
195 static void
systray_volume_scroll(GtkWidget * widget,GdkEventScroll * event,PraghaStatusIcon * status_icon)196 systray_volume_scroll (GtkWidget *widget, GdkEventScroll *event, PraghaStatusIcon *status_icon)
197 {
198 	PraghaBackend *backend;
199 
200 	if (event->type != GDK_SCROLL)
201 		return;
202 
203 	backend = pragha_application_get_backend (status_icon->pragha);
204 
205 	switch (event->direction){
206 		case GDK_SCROLL_UP:
207 			pragha_backend_set_delta_volume (backend, +0.02);
208 			break;
209 		case GDK_SCROLL_DOWN:
210 			pragha_backend_set_delta_volume (backend, -0.02);
211 			break;
212 		default:
213 			break;
214 	}
215 }
216 
217 static void
systray_about_action(GtkAction * action,PraghaStatusIcon * status_icon)218 systray_about_action (GtkAction *action, PraghaStatusIcon *status_icon)
219 {
220 	pragha_application_about_dialog (status_icon->pragha);
221 }
222 
223 static void
systray_open_file_action(GtkAction * action,PraghaStatusIcon * status_icon)224 systray_open_file_action (GtkAction *action, PraghaStatusIcon *status_icon)
225 {
226 	pragha_application_open_files (status_icon->pragha);
227 }
228 
229 static void
systray_add_location_action(GtkAction * action,PraghaStatusIcon * status_icon)230 systray_add_location_action (GtkAction *action, PraghaStatusIcon *status_icon)
231 {
232 	pragha_application_add_location (status_icon->pragha);
233 }
234 
235 static void
systray_play_pause_action(GtkAction * action,PraghaStatusIcon * status_icon)236 systray_play_pause_action (GtkAction *action, PraghaStatusIcon *status_icon)
237 {
238 	PraghaBackend *backend = pragha_application_get_backend (status_icon->pragha);
239 	if (pragha_backend_emitted_error (backend) == FALSE)
240 		pragha_playback_play_pause_resume(status_icon->pragha);
241 }
242 
243 static void
systray_stop_action(GtkAction * action,PraghaStatusIcon * status_icon)244 systray_stop_action (GtkAction *action, PraghaStatusIcon *status_icon)
245 {
246 	PraghaBackend *backend = pragha_application_get_backend (status_icon->pragha);
247 	if (pragha_backend_emitted_error (backend) == FALSE)
248 		pragha_playback_stop(status_icon->pragha);
249 }
250 
251 static void
systray_prev_action(GtkAction * action,PraghaStatusIcon * status_icon)252 systray_prev_action (GtkAction *action, PraghaStatusIcon *status_icon)
253 {
254 	PraghaBackend *backend = pragha_application_get_backend (status_icon->pragha);
255 	if (pragha_backend_emitted_error (backend) == FALSE)
256 		pragha_playback_prev_track(status_icon->pragha);
257 }
258 
259 static void
systray_next_action(GtkAction * action,PraghaStatusIcon * status_icon)260 systray_next_action (GtkAction *action, PraghaStatusIcon *status_icon)
261 {
262 	PraghaBackend *backend = pragha_application_get_backend (status_icon->pragha);
263 	if (pragha_backend_emitted_error (backend) == FALSE)
264 		pragha_playback_next_track(status_icon->pragha);
265 }
266 
267 static void
systray_edit_action(GtkAction * action,PraghaStatusIcon * status_icon)268 systray_edit_action (GtkAction *action, PraghaStatusIcon *status_icon)
269 {
270 	PraghaBackend *backend = pragha_application_get_backend (status_icon->pragha);
271 	if (pragha_backend_emitted_error (backend) == FALSE)
272 		pragha_playback_edit_current_track (status_icon->pragha);
273 }
274 
275 static void
systray_quit(GtkAction * action,PraghaStatusIcon * status_icon)276 systray_quit (GtkAction *action, PraghaStatusIcon *status_icon)
277 {
278 	pragha_application_quit (status_icon->pragha);
279 }
280 
281 static void
pragha_status_icon_update_state(PraghaBackend * backend,GParamSpec * pspec,PraghaStatusIcon * status_icon)282 pragha_status_icon_update_state (PraghaBackend *backend, GParamSpec *pspec, PraghaStatusIcon *status_icon)
283 {
284 	GtkAction *action;
285 	PraghaBackendState state = 0;
286 
287 	state = pragha_backend_get_state (backend);
288 
289 	gboolean playing = (state != ST_STOPPED);
290 
291 	action = gtk_ui_manager_get_action (status_icon->ui_manager, "/popup/Prev");
292 	gtk_action_set_sensitive (GTK_ACTION (action), playing);
293 
294 	action = gtk_ui_manager_get_action (status_icon->ui_manager, "/popup/Stop");
295 	gtk_action_set_sensitive (GTK_ACTION (action), playing);
296 
297 	action = gtk_ui_manager_get_action (status_icon->ui_manager, "/popup/Next");
298 	gtk_action_set_sensitive (GTK_ACTION (action), playing);
299 
300 	action = gtk_ui_manager_get_action (status_icon->ui_manager, "/popup/Edit tags");
301 	gtk_action_set_sensitive (GTK_ACTION (action), playing);
302 }
303 
304 static void
pragha_status_icon_set_application(PraghaStatusIcon * status_icon,PraghaApplication * pragha)305 pragha_status_icon_set_application (PraghaStatusIcon *status_icon, PraghaApplication *pragha)
306 {
307 	PraghaPreferences *preferences;
308 	GtkActionGroup *actions;
309 
310 	const GBindingFlags binding_flags =
311 		G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL;
312 
313 	status_icon->pragha = pragha;
314 
315 	gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON(status_icon), "pragha");
316 
317 	actions = gtk_action_group_new ("Systray Actions");
318 	gtk_action_group_set_translation_domain (actions, GETTEXT_PACKAGE);
319 
320 	gtk_action_group_add_actions (actions,
321 	                              systray_menu_aentries,
322 	                              G_N_ELEMENTS(systray_menu_aentries),
323 	                              (gpointer)status_icon);
324 	gtk_ui_manager_insert_action_group (status_icon->ui_manager, actions, 0);
325 
326 	preferences = pragha_application_get_preferences (pragha);
327 	g_object_bind_property (preferences, "show-status-icon",
328 	                        status_icon, "visible", binding_flags);
329 
330 	g_signal_connect (pragha_application_get_backend (pragha), "notify::state",
331 	                  G_CALLBACK (pragha_status_icon_update_state), status_icon);
332 	pragha_status_icon_update_state (pragha_application_get_backend (pragha), NULL, status_icon);
333 
334 	g_object_unref (actions);
335 }
336 
337 static void
pragha_status_icon_dispose(GObject * object)338 pragha_status_icon_dispose (GObject *object)
339 {
340 	PraghaStatusIcon *status_icon = PRAGHA_STATUS_ICON(object);
341 
342 	if (status_icon->ui_manager) {
343 		g_object_unref (status_icon->ui_manager);
344 		status_icon->ui_manager = NULL;
345 	}
346 
347 	(*G_OBJECT_CLASS (pragha_status_icon_parent_class)->dispose) (object);
348 }
349 
350 static void
pragha_status_icon_class_init(PraghaStatusIconClass * klass)351 pragha_status_icon_class_init (PraghaStatusIconClass *klass)
352 {
353 	GObjectClass  *gobject_class;
354 
355 	gobject_class = G_OBJECT_CLASS (klass);
356 	gobject_class->dispose = pragha_status_icon_dispose;
357 }
358 
359 static void
pragha_status_icon_init(PraghaStatusIcon * status_icon)360 pragha_status_icon_init (PraghaStatusIcon *status_icon)
361 {
362 	GError *error = NULL;
363 
364 	status_icon->ui_manager = gtk_ui_manager_new();
365 	if (!gtk_ui_manager_add_ui_from_string (status_icon->ui_manager, systray_menu_xml, -1, &error))
366 		g_critical("Unable to create systray menu, err : %s", error->message);
367 
368 	g_signal_connect (status_icon, "button-press-event",
369 	                  G_CALLBACK (status_icon_clicked), status_icon);
370 	g_signal_connect (status_icon, "scroll_event",
371 	                  G_CALLBACK(systray_volume_scroll), status_icon);
372 
373 	g_object_set (G_OBJECT(status_icon), "has-tooltip", TRUE, NULL);
374 	g_signal_connect (G_OBJECT(status_icon), "query-tooltip",
375 	                  G_CALLBACK(status_get_tooltip_cb), status_icon);
376 }
377 
378 PraghaStatusIcon *
pragha_status_icon_new(PraghaApplication * pragha)379 pragha_status_icon_new (PraghaApplication *pragha)
380 {
381 	PraghaStatusIcon *status_icon;
382 
383 	status_icon = g_object_new (PRAGHA_TYPE_STATUS_ICON, NULL);
384 
385 	pragha_status_icon_set_application (status_icon, pragha);
386 
387 	return status_icon;
388 }
389