seekslider.c 16.7 KB
Newer Older
1
/*
2
 *  Copyright (C) 2012-2013 Savoir-Faire Linux Inc.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 *  GStreamer plugins to be used and distributed together with GStreamer
 *  and Rhythmbox. This permission is above and beyond the permissions granted
 *  by the GPL license by which Rhythmbox is covered. If you modify this code
 *  you may extend this exception to your version of the code, but you are not
 *  obligated to do so. If you do not wish to do so, delete this exception
 *  statement from your version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 *
 */

28
29
30
31
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

32
33
34
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
35
#include <gtk/gtk.h>
36
#include <string.h>
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include "seekslider.h"
#include "dbus.h"

/**
 * SECTION:sfl-seekslider
 * @short_description: playback area widgetry
 *
 * The SFLSeekSlider widget displays information about the current playing track
 * (title, album, artist), the elapsed or remaining playback time, and a
 * position slider indicating the playback position.  It translates slider
 * move and drag events into seek requests for the player backend.
 *
 * For shoutcast-style streams, the title/artist/album display is supplemented
 * by metadata extracted from the stream.  See #RBStreamingSource for more information
 * on how the metadata is reported.
 */

#define SEEKSLIDER_INIT_VALUE 0.0
#define SEEKSLIDER_MIN_VALUE 0.0
#define SEEKSLIDER_MAX_VALUE 100.0
#define SEEKSLIDER_STEPINCREMENT 1.0
#define SEEKSLIDER_PAGEINCREMENT 1.0
#define SEEKSLIDER_PAGESIZE 1.0

61
62
63
64
65
static void sfl_seekslider_class_init(SFLSeekSliderClass *klass);
static void sfl_seekslider_init(SFLSeekSlider *seekslider);
static void sfl_seekslider_finalize(GObject *object);
static void sfl_seekslider_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
static void sfl_seekslider_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
66
67
68

static gboolean on_playback_scale_value_changed_cb(GtkRange *range, GtkScrollType scroll, gdouble value, gpointer user_data);
static gboolean on_playback_scale_pressed_cb(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
69
70
71
static gboolean on_playback_scale_released_cb(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
static gboolean on_playback_scale_moved_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
static gboolean on_playback_scale_scrolled_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
72
73
static void sfl_seekslider_play_playback_record_cb(GtkButton *button G_GNUC_UNUSED, gpointer user_data);
static void sfl_seekslider_stop_playback_record_cb(GtkButton *button G_GNUC_UNUSED, gpointer user_data);
74
75
76

struct SFLSeekSliderPrivate
{
77
    GtkWidget *hbox;
78
    GtkWidget *hscale;
79
80
81
82
    GtkWidget *playRecordWidget;
    GtkWidget *stopRecordWidget;
    GtkWidget *playRecordImage;
    GtkWidget *stopRecordImage;
83
84
    GtkWidget *separator;
    GtkWidget *separatorAlign;
85
86
87
88
    GtkWidget *timeLabel;
    guint current;
    guint size;
    gboolean is_dragging;
89
    gboolean can_update_scale;
90
    gboolean is_playing;
91
    gchar *file_path;
92
93
94
95
};

enum
{
96
    PROP_0,
97
98
    PROP_FILE_PATH,
    N_PROPERTIES
99
100
};

101
102
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

103
G_DEFINE_TYPE(SFLSeekSlider, sfl_seekslider, GTK_TYPE_HBOX)
104
105

static void
106
sfl_seekslider_class_init(SFLSeekSliderClass *klass)
107
{
108
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
109
110
111
112
113
114

    object_class->finalize = sfl_seekslider_finalize;

    object_class->set_property = sfl_seekslider_set_property;
    object_class->get_property = sfl_seekslider_get_property;

115
116
117
118
    obj_properties[PROP_FILE_PATH] = g_param_spec_string("file-path", "File path",
            "Set file path for playback", "" /* default value */, G_PARAM_READWRITE);

    g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
119
    g_type_class_add_private(klass, sizeof(SFLSeekSliderPrivate));
120
121
122
}

static void
123
sfl_seekslider_init(SFLSeekSlider *seekslider)
124
{
125
    seekslider->priv = G_TYPE_INSTANCE_GET_PRIVATE(seekslider, SFL_TYPE_SEEKSLIDER, SFLSeekSliderPrivate);
126

127
128
129
    gdouble init_value =    SEEKSLIDER_INIT_VALUE;
    gdouble min_value =     SEEKSLIDER_MIN_VALUE;
    gdouble max_value =     SEEKSLIDER_MAX_VALUE;
130
131
    gdouble stepincrement = SEEKSLIDER_STEPINCREMENT;
    gdouble pageincrement = SEEKSLIDER_PAGEINCREMENT;
132
    gdouble pagesize =      SEEKSLIDER_PAGESIZE;
133

134
135
    GtkAdjustment *adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(init_value,
                min_value, max_value, stepincrement, pageincrement, pagesize));
136
137

    seekslider->priv->hscale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, adjustment);
138
    seekslider->priv->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
139

140
141
142
143
    seekslider->priv->playRecordImage = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_BUTTON);
    seekslider->priv->playRecordWidget = gtk_button_new();
    gtk_button_set_image(GTK_BUTTON(seekslider->priv->playRecordWidget), seekslider->priv->playRecordImage);
    seekslider->priv->stopRecordImage = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PAUSE, GTK_ICON_SIZE_BUTTON);
144

145
146
147
    seekslider->priv->stopRecordWidget = gtk_button_new();
    gtk_button_set_image(GTK_BUTTON(seekslider->priv->stopRecordWidget), seekslider->priv->stopRecordImage);

148
    seekslider->priv->separator = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
149
    seekslider->priv->timeLabel = gtk_label_new("");
150
151
    seekslider->priv->separatorAlign = gtk_alignment_new(1.0, 0.75, 1.0, 1.0);

152
153
154
155
    g_signal_connect(G_OBJECT(seekslider->priv->hscale), "change-value",
                     G_CALLBACK(on_playback_scale_value_changed_cb), seekslider);

    g_signal_connect_object(G_OBJECT (seekslider->priv->hscale), "button-press-event",
156
                     G_CALLBACK(on_playback_scale_pressed_cb), seekslider, 0);
157
158

    g_signal_connect_object(G_OBJECT (seekslider->priv->hscale), "button-release-event",
159
                     G_CALLBACK(on_playback_scale_released_cb), seekslider, 0);
160
161

    g_signal_connect_object(G_OBJECT (seekslider->priv->hscale), "motion-notify-event",
162
                     G_CALLBACK(on_playback_scale_moved_cb), seekslider, 0);
163
164

    g_signal_connect_object (G_OBJECT (seekslider->priv->hscale), "scroll-event",
165
                     G_CALLBACK(on_playback_scale_scrolled_cb), seekslider, 0);
166
167
168

    g_object_set(G_OBJECT(seekslider->priv->hscale), "draw-value", FALSE, NULL);

169
    g_signal_connect_object(G_OBJECT(seekslider->priv->playRecordWidget), "pressed",
170
171
                     G_CALLBACK(sfl_seekslider_play_playback_record_cb), seekslider, 0);

172
    g_signal_connect_object(G_OBJECT(seekslider->priv->stopRecordWidget), "pressed",
173
174
                     G_CALLBACK(sfl_seekslider_stop_playback_record_cb), seekslider, 0);

175
    gtk_container_add(GTK_CONTAINER(seekslider->priv->separatorAlign), seekslider->priv->separator);
176
177
    gtk_box_pack_start(GTK_BOX(seekslider->priv->hbox), seekslider->priv->playRecordWidget, FALSE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(seekslider->priv->hbox), seekslider->priv->stopRecordWidget, FALSE, TRUE, 0);
178
    gtk_box_pack_start(GTK_BOX(seekslider->priv->hbox), seekslider->priv->separatorAlign, FALSE, TRUE, 0);
179
    gtk_box_pack_start(GTK_BOX(seekslider->priv->hbox), seekslider->priv->hscale, TRUE, TRUE, 0);
180
    gtk_box_pack_start(GTK_BOX(seekslider->priv->hbox), seekslider->priv->timeLabel, FALSE, TRUE, 0);
181

182
183
184
185
186
187
    gtk_widget_show(seekslider->priv->hbox);
    gtk_widget_show(seekslider->priv->hscale);
    gtk_widget_show(seekslider->priv->separatorAlign);
    gtk_widget_show(seekslider->priv->separator);
    gtk_widget_hide(seekslider->priv->playRecordWidget);
    gtk_widget_hide(seekslider->priv->stopRecordWidget);
188

189
    gtk_box_pack_start(GTK_BOX(&seekslider->parent), seekslider->priv->hbox, TRUE, TRUE, 0);
190
191

    seekslider->priv->can_update_scale = TRUE;
192
193
194
195
    seekslider->priv->is_dragging = FALSE;

    seekslider->priv->current = 0;
    seekslider->priv->size = 0;
196
    seekslider->priv->is_playing = FALSE;
197
    seekslider->priv->file_path = NULL;
198
199
200
}

static void
201
sfl_seekslider_finalize(GObject *object)
202
203
204
{
    SFLSeekSlider *seekslider;

205
206
    g_return_if_fail(object != NULL);
    g_return_if_fail(SFL_IS_SEEKSLIDER(object));
207

208
209
    seekslider = SFL_SEEKSLIDER(object);
    g_return_if_fail(seekslider->priv != NULL);
210

211
212
213
    /* Ensure that we've stopped playback */
    sfl_seekslider_stop_playback_record_cb(NULL, seekslider);

214
    G_OBJECT_CLASS(sfl_seekslider_parent_class)->finalize(object);
215
216
217
218
219
220
}


static void
sfl_seekslider_set_property (GObject *object, guint prop_id, const GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
{
221
222
223
224
225
    SFLSeekSlider *self = SFL_SEEKSLIDER(object);

    switch (prop_id)
    {
        case PROP_FILE_PATH:
226
227
228
229
230
231
232
233
234
            /* no change */
            if (g_strcmp0(self->priv->file_path, g_value_get_string(value)) == 0)
                break;

            /* cache is_playing as it will be modified */
            const gboolean resume_playing = self->priv->is_playing;
            if (resume_playing)
                sfl_seekslider_stop_playback_record_cb(NULL, self);

235
236
237
            g_free(self->priv->file_path);
            self->priv->file_path = g_value_dup_string(value);
            g_debug("filepath: %s\n", self->priv->file_path);
238
239
240

            if (resume_playing)
                sfl_seekslider_play_playback_record_cb(NULL, self);
241
242
243
244
245
246
247
            break;

        default:
            /* We don't have any other property... */
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
            break;
    }
248
249
250
251
252
}

static void
sfl_seekslider_get_property (GObject *object, guint prop_id, GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
{
253
254
255
256
257
258
259
260
261
262
263
264
265
    SFLSeekSlider *self = SFL_SEEKSLIDER(object);

    switch (prop_id)
    {
        case PROP_FILE_PATH:
            g_value_set_string(value, self->priv->file_path);
            break;

        default:
            /* We don't have any other property... */
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
            break;
    }
266
267
}

268

269
270
271
272
273
274
275
276
277
278
279
280
/**
 * sfl_seekslider_new:
 * @shell_player: the #RBShellPlayer instance
 * @db: the #RhythmDB instance
 *
 * Creates a new seekslider widget.
 *
 * Return value: the seekslider widget
 */
SFLSeekSlider *
sfl_seekslider_new ()
{
281
    SFLSeekSlider *seekslider = SFL_SEEKSLIDER(g_object_new(SFL_TYPE_SEEKSLIDER, NULL));
282

283
    g_return_val_if_fail(seekslider->priv != NULL, NULL);
284
285
286
287
288
289
290

    return seekslider;
}

static gboolean
on_playback_scale_value_changed_cb(GtkRange *range G_GNUC_UNUSED, GtkScrollType scroll G_GNUC_UNUSED, gdouble value, gpointer user_data G_GNUC_UNUSED)
{
291
    SFLSeekSlider *seekslider = SFL_SEEKSLIDER(user_data);
292

293
294
    dbus_set_record_playback_seek(value);

295
    guint updated_current = (guint) ((seekslider->priv->size * value) / 100.0);
296
297
    sfl_seekslider_update_timelabel(seekslider, updated_current, seekslider->priv->size);

298
299
300
301
302
303
    return FALSE;
}

static gboolean
on_playback_scale_pressed_cb(GtkWidget *widget G_GNUC_UNUSED, GdkEventButton *event, gpointer user_data)
{
304
    if (event->button == 1)
305
306
        event->button = 2;

307
    SFLSeekSlider *seekslider = SFL_SEEKSLIDER(user_data);
308
    seekslider->priv->can_update_scale = FALSE;
309
310
    seekslider->priv->is_dragging = TRUE;

311
312
313
314
    return FALSE;
}

static gboolean
315
on_playback_scale_released_cb(GtkWidget *widget G_GNUC_UNUSED, GdkEventButton *event, gpointer user_data)
316
{
317
    if (event->button == 1)
318
319
        event->button = 2;

320
    SFLSeekSlider *seekslider = SFL_SEEKSLIDER(user_data);
321
322
    seekslider->priv->can_update_scale = TRUE;

323
324
    seekslider->priv->is_dragging = FALSE;

325
326
327
328
    return FALSE;
}

static gboolean
329
on_playback_scale_moved_cb(GtkWidget *widget, GdkEvent *event G_GNUC_UNUSED, gpointer user_data)
330
{
331
    SFLSeekSlider *seekslider = SFL_SEEKSLIDER(user_data);
332

333
    if (seekslider->priv->is_dragging == FALSE)
334
335
336
337
338
339
340
        return FALSE;

    gdouble value = gtk_range_get_value(GTK_RANGE(widget));

    guint updated_current = (guint)(((gdouble)seekslider->priv->size * value) / 100.0);
    seekslider->priv->current = updated_current;

341
342
343
344
    return FALSE;
}

static gboolean
345
on_playback_scale_scrolled_cb(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED)
346
347
348
349
{
    return TRUE;
}

350
351
static void sfl_seekslider_play_playback_record_cb (GtkButton *button G_GNUC_UNUSED, gpointer user_data)
{
352
    SFLSeekSlider *self = SFL_SEEKSLIDER(user_data);
353

354
    if (self->priv->file_path == NULL || (*self->priv->file_path == 0))
355
356
        return;

357
358
    g_debug("Start file playback %s", self->priv->file_path);
    self->priv->is_playing = dbus_start_recorded_file_playback(self->priv->file_path);
359

360
361
    if (self->priv->is_playing)
        sfl_seekslider_set_display(self, SFL_SEEKSLIDER_DISPLAY_PAUSE);
362
363
364
365
}

static void sfl_seekslider_stop_playback_record_cb (GtkButton *button G_GNUC_UNUSED, gpointer user_data)
{
366
    SFLSeekSlider *self = SFL_SEEKSLIDER(user_data);
367

368
    if (self->priv->file_path == NULL || (*self->priv->file_path == 0))
369
        return;
370

371
372
373
    dbus_stop_recorded_file_playback(self->priv->file_path);
    g_debug("Stop file playback %s", self->priv->file_path);
    self->priv->is_playing = FALSE;
374

375
    sfl_seekslider_set_display(self, SFL_SEEKSLIDER_DISPLAY_PLAY);
376
377
}

378
379
380
381
382
383
384
385
386
387
void sfl_seekslider_update_timelabel(SFLSeekSlider *seekslider, guint current, guint size)
{
    gchar buf[20];
    guint current_sec = current / 1000;
    guint size_sec = size / 1000;
    guint current_min = current_sec / 60;
    guint size_min = size_sec / 60;
    current_sec = current_sec % 60;
    size_sec = size_sec % 60;

388
    if (seekslider == NULL)
389
390
        return;

391
    if (size > 0)
392
393
394
395
396
397
        g_snprintf(buf, 20, "%d:%02d / %d:%02d", current_min, current_sec, size_min, size_sec);
    else
        g_snprintf(buf, 20, "%s", "");

    gtk_label_set_text(GTK_LABEL(seekslider->priv->timeLabel), buf);
}
398

399
400
void sfl_seekslider_update_scale(SFLSeekSlider *seekslider, guint current, guint size)
{
401
    if (seekslider == NULL)
402
403
        return;

404
    if (size == 0)
405
406
        size = 1;

407
    if (current > size)
408
409
410
411
        current = size;

    gdouble val = ((gdouble) current / (gdouble) size) * 100.0;

412
    if (seekslider->priv->can_update_scale) {
413
        gtk_range_set_value(GTK_RANGE(seekslider->priv->hscale), val);
414
415
416
        sfl_seekslider_update_timelabel(seekslider, current, size);
        seekslider->priv->current = current;
        seekslider->priv->size = size;
417
        if (!seekslider->priv->is_playing) {
418
            g_warning("Seek slider state is inconsistent, updating icon");
419
420
421
422
423
            /* State somehow become inconsistent: the seekbar is moving but
             * the play icon is not set to paused */
            seekslider->priv->is_playing = TRUE;
            sfl_seekslider_set_display(seekslider, SFL_SEEKSLIDER_DISPLAY_PAUSE);
        }
424
    }
425
}
426
427
428

void sfl_seekslider_set_display(SFLSeekSlider *seekslider, SFLSeekSliderDisplay display) {

429
430
431
    if (seekslider == NULL || !seekslider->priv ||
        !GTK_IS_WIDGET(seekslider->priv->playRecordWidget) ||
        !GTK_IS_WIDGET(seekslider->priv->stopRecordWidget))
432
433
        return;

434
435
436
437
438
439
440
441
442
443
    switch (display) {
        case SFL_SEEKSLIDER_DISPLAY_PAUSE:
            gtk_widget_hide(seekslider->priv->playRecordWidget);
            gtk_widget_show(seekslider->priv->stopRecordWidget);
            break;
        case SFL_SEEKSLIDER_DISPLAY_PLAY:
            gtk_widget_hide(seekslider->priv->stopRecordWidget);
            gtk_widget_show(seekslider->priv->playRecordWidget);
            break;
        default:
444
            g_warning("Unknown display option for seekslider");
445
            break;
446
447
    }
}
448

449
450
451
void sfl_seekslider_reset(SFLSeekSlider *seekslider)
{
    if (seekslider == NULL)
452
453
        return;

454
    seekslider->priv->can_update_scale = FALSE;
455
456
    gtk_range_set_value(GTK_RANGE(seekslider->priv->hscale), 0.0);
    sfl_seekslider_set_display(seekslider, SFL_SEEKSLIDER_DISPLAY_PLAY);
457
458
    if (seekslider->priv->is_playing)
        sfl_seekslider_stop_playback_record_cb(NULL, seekslider);
459
460
461
    gtk_label_set_text(GTK_LABEL(seekslider->priv->timeLabel), "");
    seekslider->priv->current = 0;
    seekslider->priv->size = 0;
462
    seekslider->priv->is_playing = FALSE;
463
    seekslider->priv->can_update_scale = TRUE;
464
}
465
466
467
468
469
470

gboolean
sfl_seekslider_has_path(SFLSeekSlider *seekslider, const gchar *file_path)
{
    return g_strcmp0(seekslider->priv->file_path, file_path) == 0;
}