1 /*
2  *  Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
10  *  GStreamer plugins to be used and distributed together with GStreamer
11  *  and Rhythmbox. This permission is above and beyond the permissions granted
12  *  by the GPL license by which Rhythmbox is covered. If you modify this code
13  *  you may extend this exception to your version of the code, but you are not
14  *  obligated to do so. If you do not wish to do so, delete this exception
15  *  statement from your version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
25  *
26  */
27 
28 #include "config.h"
29 
30 #include <glib/gi18n.h>
31 
32 #include "rb-sync-state-ui.h"
33 #include "rb-debug.h"
34 #include "rb-util.h"
35 #include "rb-file-helpers.h"
36 #include "rb-builder-helpers.h"
37 
38 #include "rb-segmented-bar.h"
39 
40 struct _RBSyncStateUIPrivate
41 {
42 	/* we don't own a reference on this (maybe we should?) */
43 	RBSyncState *state;
44 
45 	/* or any of these */
46 	GtkWidget *add_count;
47 	GtkWidget *remove_count;
48 	RBSyncBarData sync_before;
49 	RBSyncBarData sync_after;
50 };
51 
52 enum {
53 	PROP_0,
54 	PROP_SYNC_STATE
55 };
56 
G_DEFINE_TYPE(RBSyncStateUI,rb_sync_state_ui,GTK_TYPE_BOX)57 G_DEFINE_TYPE (RBSyncStateUI, rb_sync_state_ui, GTK_TYPE_BOX)
58 
59 
60 static char *
61 value_formatter (gdouble percent, RBSyncBarData *bar)
62 {
63 	return g_format_size (percent * bar->capacity);
64 }
65 
66 void
rb_sync_state_ui_create_bar(RBSyncBarData * bar,guint64 capacity,GtkWidget * label)67 rb_sync_state_ui_create_bar (RBSyncBarData *bar, guint64 capacity, GtkWidget *label)
68 {
69 	bar->widget = rb_segmented_bar_new ();
70 	bar->capacity = capacity;
71 	g_object_set (bar->widget, "show-labels", TRUE, NULL);
72 
73 	rb_segmented_bar_set_value_formatter (RB_SEGMENTED_BAR (bar->widget),
74 					      (RBSegmentedBarValueFormatter) value_formatter,
75 					      bar);
76 
77 	bar->music_segment = rb_segmented_bar_add_segment (RB_SEGMENTED_BAR (bar->widget),_("Music"), 0.0, 0.2, 0.4, 0.65, 1.0);
78 	bar->podcast_segment = rb_segmented_bar_add_segment (RB_SEGMENTED_BAR (bar->widget), _("Podcasts"), 0.0, 0.96, 0.47, 0.0, 1.0);
79 	bar->other_segment = rb_segmented_bar_add_segment (RB_SEGMENTED_BAR (bar->widget), _("Other"), 0.0, 0.45, 0.82, 0.08, 1.0);
80 	bar->free_segment = rb_segmented_bar_add_segment_default_color (RB_SEGMENTED_BAR (bar->widget), _("Available"), 1.0);
81 
82 	/* set up label relationship */
83 	if (label != NULL) {
84 		AtkObject *lobj;
85 		AtkObject *robj;
86 
87 		lobj = gtk_widget_get_accessible (label);
88 		robj = gtk_widget_get_accessible (bar->widget);
89 
90 		atk_object_add_relationship (lobj, ATK_RELATION_LABEL_FOR, robj);
91 		atk_object_add_relationship (robj, ATK_RELATION_LABELLED_BY, lobj);
92 	}
93 }
94 
95 
96 void
rb_sync_state_ui_update_volume_usage(RBSyncBarData * bar,RBSyncState * state)97 rb_sync_state_ui_update_volume_usage (RBSyncBarData *bar, RBSyncState *state)
98 {
99 	RBMediaPlayerSource *source;
100 	double fraction;
101 	guint64 total_other;
102 	guint64 free_space;
103 
104 	g_object_get (state, "source", &source, NULL);
105 	free_space = rb_media_player_source_get_free_space (source);
106 	g_object_unref (source);
107 
108 	total_other = bar->capacity - (free_space + state->total_music_size + state->total_podcast_size);
109 
110 	fraction = (double)state->total_music_size/(double)bar->capacity;
111 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
112 					 bar->music_segment,
113 					 fraction);
114 	fraction = (double)state->total_podcast_size/(double)bar->capacity;
115 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
116 					 bar->podcast_segment,
117 					 fraction);
118 	fraction = (double)total_other/(double)bar->capacity;
119 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
120 					 bar->other_segment,
121 					 fraction);
122 	fraction = (double)free_space/(double)bar->capacity;
123 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
124 					 bar->free_segment,
125 					 fraction);
126 }
127 
128 static void
update_sync_after_bar(RBSyncBarData * bar,RBSyncState * state)129 update_sync_after_bar (RBSyncBarData *bar, RBSyncState *state)
130 {
131 	RBMediaPlayerSource *source;
132 	RBSyncSettings *settings;
133 	double music_fraction;
134 	double podcast_fraction;
135 	double other_fraction;
136 	double free_fraction;
137 	guint64 total_other_size;
138 
139 	g_object_get (state,
140 		      "source", &source,
141 		      "sync-settings", &settings,
142 		      NULL);
143 
144 	if (rb_sync_settings_has_enabled_groups (settings, SYNC_CATEGORY_MUSIC) ||
145 	    rb_sync_settings_sync_category (settings, SYNC_CATEGORY_MUSIC)) {
146 		music_fraction = (double)state->sync_music_size / (double)bar->capacity;
147 	} else {
148 		music_fraction = (double)state->total_music_size / (double)bar->capacity;
149 	}
150 	if (rb_sync_settings_has_enabled_groups (settings, SYNC_CATEGORY_PODCAST) ||
151 	    rb_sync_settings_sync_category (settings, SYNC_CATEGORY_PODCAST)) {
152 		podcast_fraction = (double)state->sync_podcast_size / (double)bar->capacity;
153 	} else {
154 		podcast_fraction = (double)state->total_podcast_size / (double)bar->capacity;
155 	}
156 
157 	total_other_size = bar->capacity - (rb_media_player_source_get_free_space (source) + state->total_music_size + state->total_podcast_size);
158 	other_fraction = (double)total_other_size / (double)bar->capacity;
159 
160 	free_fraction = 1.0 - (music_fraction + podcast_fraction + other_fraction);
161 	if (free_fraction < 0.0) {
162 		free_fraction = 0.0;
163 	}
164 
165 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
166 					 bar->music_segment,
167 					 music_fraction);
168 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
169 					 bar->podcast_segment,
170 					 podcast_fraction);
171 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
172 					 bar->other_segment,
173 					 other_fraction);
174 	rb_segmented_bar_update_segment (RB_SEGMENTED_BAR (bar->widget),
175 					 bar->free_segment,
176 					 free_fraction);
177 
178 	g_object_unref (source);
179 	g_object_unref (settings);
180 }
181 
182 static void
sync_state_updated(RBSyncState * state,RBSyncStateUI * ui)183 sync_state_updated (RBSyncState *state, RBSyncStateUI *ui)
184 {
185 	char *text;
186 	rb_debug ("sync state updated");
187 
188 	/* sync before state */
189 	rb_sync_state_ui_update_volume_usage (&ui->priv->sync_before, state);
190 	update_sync_after_bar (&ui->priv->sync_after, state);
191 
192 	/* other stuff */
193 	text = g_strdup_printf ("%d", state->sync_add_count);
194 	gtk_label_set_text (GTK_LABEL (ui->priv->add_count), text);
195 	g_free (text);
196 
197 	text = g_strdup_printf ("%d", state->sync_remove_count);
198 	gtk_label_set_text (GTK_LABEL (ui->priv->remove_count), text);
199 	g_free (text);
200 }
201 
202 
203 GtkWidget *
rb_sync_state_ui_new(RBSyncState * state)204 rb_sync_state_ui_new (RBSyncState *state)
205 {
206 	GObject *ui;
207 	ui = g_object_new (RB_TYPE_SYNC_STATE_UI,
208 			   "sync-state", state,
209 			   NULL);
210 	return GTK_WIDGET (ui);
211 }
212 
213 static void
rb_sync_state_ui_init(RBSyncStateUI * ui)214 rb_sync_state_ui_init (RBSyncStateUI *ui)
215 {
216 	ui->priv = G_TYPE_INSTANCE_GET_PRIVATE (ui, RB_TYPE_SYNC_STATE_UI, RBSyncStateUIPrivate);
217 	gtk_orientable_set_orientation (GTK_ORIENTABLE (ui), GTK_ORIENTATION_VERTICAL);
218 }
219 
220 static void
build_ui(RBSyncStateUI * ui)221 build_ui (RBSyncStateUI *ui)
222 {
223 	RBMediaPlayerSource *source;
224 	GtkWidget *widget;
225 	GtkWidget *container;
226 	guint64 capacity;
227 	GtkBuilder *builder;
228 
229 	g_object_get (ui->priv->state, "source", &source, NULL);
230 	capacity = rb_media_player_source_get_capacity (source);
231 	g_object_unref (source);
232 
233 	builder = rb_builder_load ("sync-state.ui", NULL);
234 	if (builder == NULL) {
235 		g_warning ("Couldn't load sync-state.ui");
236 		return;
237 	}
238 
239 	container = GTK_WIDGET (gtk_builder_get_object (builder, "sync-state-ui"));
240 	gtk_box_pack_start (GTK_BOX (ui), container, TRUE, TRUE, 0);
241 
242 	ui->priv->add_count = GTK_WIDGET (gtk_builder_get_object (builder, "added-tracks"));
243 	ui->priv->remove_count = GTK_WIDGET (gtk_builder_get_object (builder, "removed-tracks"));
244 
245 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "sync-before-label"));
246 	rb_sync_state_ui_create_bar (&ui->priv->sync_before, capacity, widget);
247 	container = GTK_WIDGET (gtk_builder_get_object (builder, "sync-before-container"));
248 	gtk_container_add (GTK_CONTAINER (container), ui->priv->sync_before.widget);
249 
250 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "sync-after-label"));
251 	rb_sync_state_ui_create_bar (&ui->priv->sync_after, capacity, widget);
252 	container = GTK_WIDGET (gtk_builder_get_object (builder, "sync-after-container"));
253 	gtk_container_add (GTK_CONTAINER (container), ui->priv->sync_after.widget);
254 
255 	g_object_unref (builder);
256 }
257 
258 static void
impl_constructed(GObject * object)259 impl_constructed (GObject *object)
260 {
261 	RBSyncStateUI *ui = RB_SYNC_STATE_UI (object);
262 
263 	build_ui (ui);
264 	sync_state_updated (ui->priv->state, ui);
265 
266 	g_signal_connect_object (ui->priv->state,
267 				 "updated",
268 				 G_CALLBACK (sync_state_updated),
269 				 ui, 0);
270 
271 	RB_CHAIN_GOBJECT_METHOD(rb_sync_state_ui_parent_class, constructed, object);
272 }
273 
274 
275 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)276 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
277 {
278 	RBSyncStateUI *ui = RB_SYNC_STATE_UI (object);
279 	switch (prop_id) {
280 	case PROP_SYNC_STATE:
281 		ui->priv->state = g_value_get_object (value);
282 		break;
283 	default:
284 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
285 		break;
286 	}
287 }
288 
289 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)290 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
291 {
292 	RBSyncStateUI *ui = RB_SYNC_STATE_UI (object);
293 	switch (prop_id) {
294 	case PROP_SYNC_STATE:
295 		g_value_set_object (value, ui->priv->state);
296 		break;
297 	default:
298 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
299 		break;
300 	}
301 }
302 
303 
304 static void
rb_sync_state_ui_class_init(RBSyncStateUIClass * klass)305 rb_sync_state_ui_class_init (RBSyncStateUIClass *klass)
306 {
307 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
308 
309 	object_class->constructed = impl_constructed;
310 	object_class->set_property = impl_set_property;
311 	object_class->get_property = impl_get_property;
312 
313 	g_object_class_install_property (object_class,
314 					 PROP_SYNC_STATE,
315 					 g_param_spec_object ("sync-state",
316 							      "sync-state",
317 							      "RBSyncState instance",
318 							      RB_TYPE_SYNC_STATE,
319 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
320 
321 	g_type_class_add_private (object_class, sizeof (RBSyncStateUIPrivate));
322 }
323