summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtkmediacontrols.c501
-rw-r--r--gtk/gtkmediacontrols.h45
-rw-r--r--gtk/meson.build2
-rw-r--r--gtk/ui/gtkmediacontrols.ui64
5 files changed, 613 insertions, 0 deletions
diff --git a/gtk/gtk.h b/gtk/gtk.h
index bc1c62c180..afe849c292 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -137,6 +137,7 @@
#include <gtk/gtkliststore.h>
#include <gtk/gtklockbutton.h>
#include <gtk/gtkmain.h>
+#include <gtk/gtkmediacontrols.h>
#include <gtk/gtkmediafile.h>
#include <gtk/gtkmediastream.h>
#include <gtk/gtkmenu.h>
diff --git a/gtk/gtkmediacontrols.c b/gtk/gtkmediacontrols.c
new file mode 100644
index 0000000000..1f299bd2f2
--- /dev/null
+++ b/gtk/gtkmediacontrols.c
@@ -0,0 +1,501 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtkmediacontrols.h"
+
+#include "gtkadjustment.h"
+#include "gtkbutton.h"
+#include "gtkintl.h"
+#include "gtklabel.h"
+
+/**
+ * SECTION:gtkmediacontrols
+ * @title: GtkMediaControls
+ * @short_description: A widget showing controls for a media stream
+ *
+ * GtkMediaControls is a widget to show controls for a #GtkMediaStream
+ * and giving users a way to use it.
+ */
+
+struct _GtkMediaControls
+{
+ GtkWidget parent_instance;
+
+ GtkMediaStream *stream;
+
+ GtkAdjustment *time_adjustment;
+ GtkAdjustment *volume_adjustment;
+ GtkWidget *box;
+ GtkWidget *play_button;
+ GtkWidget *time_box;
+ GtkWidget *time_label;
+ GtkWidget *seek_scale;
+ GtkWidget *duration_label;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MEDIA_STREAM,
+
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkMediaControls, gtk_media_controls, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+/* FIXME: Remove
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=679850 */
+static char *
+totem_time_to_string (gint64 usecs,
+ gboolean remaining,
+ gboolean force_hour)
+{
+ int sec, min, hour, _time;
+
+ _time = (int) (usecs / G_USEC_PER_SEC);
+ /* When calculating the remaining time,
+ * we want to make sure that:
+ * current time + time remaining = total run time */
+ if (remaining)
+ _time++;
+
+ sec = _time % 60;
+ _time = _time - sec;
+ min = (_time % (60*60)) / 60;
+ _time = _time - (min * 60);
+ hour = _time / (60*60);
+
+ if (hour > 0 || force_hour) {
+ if (!remaining) {
+ /* hour:minutes:seconds */
+ /* Translators: This is a time format, like "-9:05:02" for 9
+ * hours, 5 minutes, and 2 seconds. You may change ":" to
+ * the separator that your locale uses or use "%Id" instead
+ * of "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_("long time format", "%d:%02d:%02d"), hour, min, sec);
+ } else {
+ /* -hour:minutes:seconds */
+ /* Translators: This is a time format, like "-9:05:02" for 9
+ * hours, 5 minutes, and 2 seconds playback remaining. You may
+ * change ":" to the separator that your locale uses or use
+ * "%Id" instead of "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_("long time format", "-%d:%02d:%02d"), hour, min, sec);
+ }
+ }
+
+ if (remaining) {
+ /* -minutes:seconds */
+ /* Translators: This is a time format, like "-5:02" for 5
+ * minutes and 2 seconds playback remaining. You may change
+ * ":" to the separator that your locale uses or use "%Id"
+ * instead of "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_("short time format", "-%d:%02d"), min, sec);
+ }
+
+ /* minutes:seconds */
+ /* Translators: This is a time format, like "5:02" for 5
+ * minutes and 2 seconds. You may change ":" to the
+ * separator that your locale uses or use "%Id" instead of
+ * "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_("short time format", "%d:%02d"), min, sec);
+}
+
+static void
+time_adjustment_changed (GtkAdjustment *adjustment,
+ GtkMediaControls *controls)
+{
+ if (controls->stream == NULL)
+ return;
+
+ /* We just updated the adjustment and it's correct now */
+ if (gtk_adjustment_get_value (adjustment) == (double) gtk_media_stream_get_timestamp (controls->stream) / G_USEC_PER_SEC)
+ return;
+
+ gtk_media_stream_seek (controls->stream,
+ gtk_adjustment_get_value (adjustment) * G_USEC_PER_SEC + 0.5);
+}
+
+static void
+volume_adjustment_changed (GtkAdjustment *adjustment,
+ GtkMediaControls *controls)
+{
+ if (controls->stream == NULL)
+ return;
+
+ /* We just updated the adjustment and it's correct now */
+ if (gtk_adjustment_get_value (adjustment) == gtk_media_stream_get_volume (controls->stream))
+ return;
+
+ gtk_media_stream_set_muted (controls->stream, gtk_adjustment_get_value (adjustment) == 0.0);
+ gtk_media_stream_set_volume (controls->stream, gtk_adjustment_get_value (adjustment));
+}
+
+static void
+play_button_clicked (GtkWidget *button,
+ GtkMediaControls *controls)
+{
+ if (controls->stream == NULL)
+ return;
+
+ gtk_media_stream_set_playing (controls->stream,
+ !gtk_media_stream_get_playing (controls->stream));
+}
+
+static void
+gtk_media_controls_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
+
+ gtk_widget_measure (controls->box,
+ orientation,
+ for_size,
+ minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void
+gtk_media_controls_size_allocate (GtkWidget *widget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip)
+{
+ GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
+
+ gtk_widget_size_allocate (controls->box, allocation, baseline, out_clip);
+}
+
+static void
+gtk_media_controls_dispose (GObject *object)
+{
+ GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
+
+ gtk_media_controls_set_media_stream (controls, NULL);
+
+ g_clear_pointer (&controls->box, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (gtk_media_controls_parent_class)->dispose (object);
+}
+
+static void
+gtk_media_controls_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
+
+ switch (property_id)
+ {
+ case PROP_MEDIA_STREAM:
+ g_value_set_object (value, controls->stream);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_media_controls_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
+
+ switch (property_id)
+ {
+ case PROP_MEDIA_STREAM:
+ gtk_media_controls_set_media_stream (controls, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_media_controls_class_init (GtkMediaControlsClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ widget_class->measure = gtk_media_controls_measure;
+ widget_class->size_allocate = gtk_media_controls_size_allocate;
+
+ gobject_class->dispose = gtk_media_controls_dispose;
+ gobject_class->get_property = gtk_media_controls_get_property;
+ gobject_class->set_property = gtk_media_controls_set_property;
+
+ /**
+ * GtkMediaControls:media-stream:
+ *
+ * The media-stream managed by this object or %NULL if none.
+ */
+ properties[PROP_MEDIA_STREAM] =
+ g_param_spec_object ("media-stream",
+ P_("Media Stream"),
+ P_("The media stream managed"),
+ GTK_TYPE_MEDIA_STREAM,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkmediacontrols.ui");
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, volume_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, box);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, play_button);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_box);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_label);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, seek_scale);
+ gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, duration_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, play_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, time_adjustment_changed);
+ gtk_widget_class_bind_template_callback (widget_class, volume_adjustment_changed);
+
+ gtk_widget_class_set_css_name (widget_class, I_("controls"));
+}
+
+static void
+gtk_media_controls_init (GtkMediaControls *controls)
+{
+ gtk_widget_init_template (GTK_WIDGET (controls));
+ gtk_widget_set_has_window (GTK_WIDGET (controls), FALSE);
+}
+
+/**
+ * gtk_media_controls_new:
+ * @stream: (allow-none) (transfer none): a #GtkMediaStream to
+ * manage or %NULL for none.
+ *
+ * Creates a new #GtkMediaControls managing the @stream passed to it.
+ *
+ * Returns: a new #GtkMediaControls
+ **/
+GtkWidget *
+gtk_media_controls_new (GtkMediaStream *stream)
+{
+ return g_object_new (GTK_TYPE_MEDIA_CONTROLS,
+ "media-stream", stream,
+ NULL);
+}
+
+/**
+ * gtk_media_controls_get_media_stream:
+ * @controls: a #GtkMediaControls
+ *
+ * Gets the media stream managed by @controls or %NULL if none.
+ *
+ * Returns: (nullable): The media stream managed by @controls
+ **/
+GtkMediaStream *
+gtk_media_controls_get_media_stream (GtkMediaControls *controls)
+{
+ g_return_val_if_fail (GTK_IS_MEDIA_CONTROLS (controls), NULL);
+
+ return controls->stream;
+}
+
+static void
+update_timestamp (GtkMediaControls *controls)
+{
+ gint64 timestamp, duration;
+ char *time_string;
+
+ if (controls->stream)
+ {
+ timestamp = gtk_media_stream_get_timestamp (controls->stream);
+ duration = gtk_media_stream_get_duration (controls->stream);
+ }
+ else
+ {
+ timestamp = 0;
+ duration = 0;
+ }
+
+ time_string = totem_time_to_string (timestamp, FALSE, FALSE);
+ gtk_label_set_text (GTK_LABEL (controls->time_label), time_string);
+ g_free (time_string);
+
+ if (duration > 0)
+ {
+ time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
+ gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
+ g_free (time_string);
+
+ gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
+ }
+}
+
+static void
+update_duration (GtkMediaControls *controls)
+{
+ gint64 timestamp, duration;
+ char *time_string;
+
+ if (controls->stream)
+ {
+ timestamp = gtk_media_stream_get_timestamp (controls->stream);
+ duration = gtk_media_stream_get_duration (controls->stream);
+ }
+ else
+ {
+ timestamp = 0;
+ duration = 0;
+ }
+
+ time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
+ gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
+ gtk_widget_set_visible (controls->duration_label, duration > 0);
+ g_free (time_string);
+
+ gtk_adjustment_set_upper (controls->time_adjustment,
+ gtk_adjustment_get_page_size (controls->time_adjustment)
+ + (double) duration / G_USEC_PER_SEC);
+ gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
+}
+
+static void
+update_playing (GtkMediaControls *controls)
+{
+ gboolean playing;
+ const char *icon_name;
+
+ if (controls->stream)
+ playing = gtk_media_stream_get_playing (controls->stream);
+ else
+ playing = FALSE;
+
+ if (playing)
+ icon_name = "media-playback-pause-symbolic";
+ else
+ icon_name = "media-playback-start-symbolic";
+
+ gtk_button_set_icon_name (GTK_BUTTON (controls->play_button), icon_name);
+}
+
+static void
+update_seekable (GtkMediaControls *controls)
+{
+ gboolean seekable;
+
+ if (controls->stream)
+ seekable = gtk_media_stream_is_seekable (controls->stream);
+ else
+ seekable = FALSE;
+
+ gtk_widget_set_sensitive (controls->seek_scale, seekable);
+}
+
+static void
+update_volume (GtkMediaControls *controls)
+{
+ double volume;
+
+ if (controls->stream == NULL)
+ volume = 1.0;
+ else if (gtk_media_stream_get_muted (controls->stream))
+ volume = 0.0;
+ else
+ volume = gtk_media_stream_get_volume (controls->stream);
+
+ gtk_adjustment_set_value (controls->volume_adjustment, volume);
+}
+
+static void
+update_all (GtkMediaControls *controls)
+{
+ update_timestamp (controls);
+ update_duration (controls);
+ update_playing (controls);
+ update_seekable (controls);
+ update_volume (controls);
+}
+
+static void
+gtk_media_controls_notify_cb (GtkMediaStream *stream,
+ GParamSpec *pspec,
+ GtkMediaControls *controls)
+{
+ if (g_str_equal (pspec->name, "timestamp"))
+ update_timestamp (controls);
+ else if (g_str_equal (pspec->name, "duration"))
+ update_duration (controls);
+ else if (g_str_equal (pspec->name, "playing"))
+ update_playing (controls);
+ else if (g_str_equal (pspec->name, "seekable"))
+ update_seekable (controls);
+ else if (g_str_equal (pspec->name, "muted"))
+ update_volume (controls);
+ else if (g_str_equal (pspec->name, "volume"))
+ update_volume (controls);
+}
+
+void
+gtk_media_controls_set_media_stream (GtkMediaControls *controls,
+ GtkMediaStream *stream)
+{
+ g_return_if_fail (GTK_IS_MEDIA_CONTROLS (controls));
+ g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
+
+ if (controls->stream == stream)
+ return;
+
+ if (controls->stream)
+ {
+ g_signal_handlers_disconnect_by_func (controls->stream,
+ gtk_media_controls_notify_cb,
+ controls);
+ g_object_unref (controls->stream);
+ controls->stream = NULL;
+ }
+
+ if (stream)
+ {
+ controls->stream = g_object_ref (stream);
+ g_signal_connect (controls->stream,
+ "notify",
+ G_CALLBACK (gtk_media_controls_notify_cb),
+ controls);
+ }
+
+ update_all (controls);
+ gtk_widget_set_sensitive (controls->box, stream != NULL);
+
+ g_object_notify_by_pspec (G_OBJECT (controls), properties[PROP_MEDIA_STREAM]);
+}
+
diff --git a/gtk/gtkmediacontrols.h b/gtk/gtkmediacontrols.h
new file mode 100644
index 0000000000..be399364ec
--- /dev/null
+++ b/gtk/gtkmediacontrols.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_MEDIA_CONTROLS_H__
+#define __GTK_MEDIA_CONTROLS_H__
+
+#include <gtk/gtkmediastream.h>
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_MEDIA_CONTROLS (gtk_media_controls_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkMediaControls, gtk_media_controls, GTK, MEDIA_CONTROLS, GtkWidget)
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget *gtk_media_controls_new (GtkMediaStream *stream);
+
+GDK_AVAILABLE_IN_ALL
+GtkMediaStream *gtk_media_controls_get_media_stream (GtkMediaControls *controls);
+GDK_AVAILABLE_IN_ALL
+void gtk_media_controls_set_media_stream (GtkMediaControls *controls,
+ GtkMediaStream *stream);
+
+
+G_END_DECLS
+
+#endif /* __GTK_MEDIA_CONTROLS_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index f62afe4c3d..22d849f2e9 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -251,6 +251,7 @@ gtk_public_sources = files([
'gtkliststore.c',
'gtklockbutton.c',
'gtkmain.c',
+ 'gtkmediacontrols.c',
'gtkmediafile.c',
'gtkmediastream.c',
'gtkmenu.c',
@@ -483,6 +484,7 @@ gtk_public_headers = files([
'gtkliststore.h',
'gtklockbutton.h',
'gtkmain.h',
+ 'gtkmediacontrols.h',
'gtkmediafile.h',
'gtkmediastream.h',
'gtkmenu.h',
diff --git a/gtk/ui/gtkmediacontrols.ui b/gtk/ui/gtkmediacontrols.ui
new file mode 100644
index 0000000000..895259dc50
--- /dev/null
+++ b/gtk/ui/gtkmediacontrols.ui
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <!-- interface-requires gtk+ 3.6 -->
+ <object class="GtkAdjustment" id="time_adjustment">
+ <property name="upper">10</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <signal name="value-changed" handler="time_adjustment_changed" object="GtkMediaControls" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="volume_adjustment">
+ <property name="upper">1</property>
+ <property name="step-increment">0.1</property>
+ <property name="page-increment">1</property>
+ <property name="value">1</property>
+ <signal name="value-changed" handler="volume_adjustment_changed" object="GtkMediaControls" swapped="no"/>
+ </object>
+ <template class="GtkMediaControls" parent="GtkWidget">
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="hexpand">0</property>
+ <property name="sensitive">0</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="play_button">
+ <property name="can-focus">1</property>
+ <property name="receives-default">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="icon-name">media-playback-start-symbolic</property>
+ <signal name="clicked" handler="play_button_clicked" object="GtkMediaControls" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="time_box">
+ <child>
+ <object class="GtkLabel" id="time_label">
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScale" id="seek_scale">
+ <property name="adjustment">time_adjustment</property>
+ <property name="can_focus">True</property>
+ <property name="draw_value">False</property>
+ <property name="restrict-to-fill-level">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="duration_label">
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVolumeButton" id="volume_button">
+ <property name="adjustment">volume_adjustment</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>