mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 08:21:59 +02:00
A rewritten Clapper video player made using "Clapper" and "ClapperGtk" libraries. Since both libraries from this repo are in C, newly rewritten Clapper binary is also in C to avoid mixing different programming languages in a single repo, thus making maintenance easier. Not depending on GJS gives us also an additional benefit of supporting different operating systems or linux shells without pulling GJS as dependency. Licensed under GPL-3.0-or-later.
365 lines
11 KiB
C
365 lines
11 KiB
C
/* Clapper Application
|
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* 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 3 of the License, or
|
|
* (at your option) any later 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <gst/gst.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "clapper-app-queue-selection.h"
|
|
|
|
#define GST_CAT_DEFAULT clapper_app_queue_selection_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
struct _ClapperAppQueueSelection
|
|
{
|
|
GObject parent;
|
|
|
|
ClapperQueue *queue;
|
|
|
|
ClapperMediaItem *current_item;
|
|
guint current_position;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_QUEUE,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_ITEM_SELECTED,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
|
static guint signals[SIGNAL_LAST] = { 0, };
|
|
|
|
static GType
|
|
clapper_app_queue_selection_get_item_type (GListModel *model)
|
|
{
|
|
return CLAPPER_TYPE_MEDIA_ITEM;
|
|
}
|
|
|
|
static guint
|
|
clapper_app_queue_selection_get_n_items (GListModel *model)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model);
|
|
|
|
return (self->queue) ? clapper_queue_get_n_items (self->queue) : 0;
|
|
}
|
|
|
|
static gpointer
|
|
clapper_app_queue_selection_get_item (GListModel *model, guint index)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model);
|
|
|
|
return (self->queue) ? clapper_queue_get_item (self->queue, index) : NULL;
|
|
}
|
|
|
|
static void
|
|
_list_model_iface_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_item_type = clapper_app_queue_selection_get_item_type;
|
|
iface->get_n_items = clapper_app_queue_selection_get_n_items;
|
|
iface->get_item = clapper_app_queue_selection_get_item;
|
|
}
|
|
|
|
static inline void
|
|
_refresh_current_selection (ClapperAppQueueSelection *self)
|
|
{
|
|
guint position, old_position, index, n_changed;
|
|
|
|
position = clapper_queue_get_current_index (self->queue);
|
|
|
|
/* Clapper -> GTK expected value change.
|
|
* Should be the same, but better be safe. */
|
|
if (position == CLAPPER_QUEUE_INVALID_POSITION)
|
|
position = GTK_INVALID_LIST_POSITION;
|
|
|
|
/* No change */
|
|
if (position == self->current_position)
|
|
return;
|
|
|
|
old_position = self->current_position;
|
|
self->current_position = position;
|
|
|
|
if (old_position == GTK_INVALID_LIST_POSITION) {
|
|
index = position;
|
|
n_changed = 1;
|
|
} else if (position == GTK_INVALID_LIST_POSITION) {
|
|
index = old_position;
|
|
n_changed = 1;
|
|
} else if (position < old_position) {
|
|
index = position;
|
|
n_changed = old_position - position + 1;
|
|
} else {
|
|
index = old_position;
|
|
n_changed = position - old_position + 1;
|
|
}
|
|
|
|
GST_DEBUG ("Selection changed, index: %u, n_changed: %u", index, n_changed);
|
|
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), index, n_changed);
|
|
}
|
|
|
|
static gboolean
|
|
clapper_app_queue_selection_is_selected (GtkSelectionModel *model, guint position)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model);
|
|
|
|
return (position == self->current_position);
|
|
}
|
|
|
|
static GtkBitset *
|
|
clapper_app_queue_selection_get_selection_in_range (GtkSelectionModel *model, guint position, guint n_items)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model);
|
|
GtkBitset *bitset = gtk_bitset_new_empty ();
|
|
|
|
if (self->current_position != GTK_INVALID_LIST_POSITION
|
|
&& position <= self->current_position
|
|
&& position + n_items > self->current_position)
|
|
gtk_bitset_add (bitset, self->current_position);
|
|
|
|
return bitset;
|
|
}
|
|
|
|
static gboolean
|
|
clapper_app_queue_selection_select_item (GtkSelectionModel *model, guint position, gboolean exclusive)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model);
|
|
gboolean res = TRUE;
|
|
|
|
if (G_UNLIKELY (self->queue == NULL))
|
|
return FALSE;
|
|
|
|
/* Disallow reselecting of the same item */
|
|
if (self->current_position != position)
|
|
res = clapper_queue_select_index (self->queue, position);
|
|
|
|
/* Need to always emit this signal when select item succeeds */
|
|
if (G_LIKELY (res))
|
|
g_signal_emit (self, signals[SIGNAL_ITEM_SELECTED], 0, position);
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
clapper_app_queue_selection_unselect_item (GtkSelectionModel *model, guint position)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_selection_model_iface_init (GtkSelectionModelInterface *iface)
|
|
{
|
|
iface->is_selected = clapper_app_queue_selection_is_selected;
|
|
iface->get_selection_in_range = clapper_app_queue_selection_get_selection_in_range;
|
|
iface->select_item = clapper_app_queue_selection_select_item;
|
|
iface->unselect_item = clapper_app_queue_selection_unselect_item;
|
|
}
|
|
|
|
#define parent_class clapper_app_queue_selection_parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (ClapperAppQueueSelection, clapper_app_queue_selection, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, _list_model_iface_init)
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, _selection_model_iface_init))
|
|
|
|
static void
|
|
_queue_model_items_changed_cb (GListModel *model, guint position, guint removed, guint added,
|
|
ClapperAppQueueSelection *self)
|
|
{
|
|
/* Forward event from internal model */
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
|
|
}
|
|
|
|
static void
|
|
_queue_current_index_changed_cb (ClapperQueue *queue,
|
|
GParamSpec *pspec G_GNUC_UNUSED, ClapperAppQueueSelection *self)
|
|
{
|
|
_refresh_current_selection (self);
|
|
}
|
|
|
|
/*
|
|
* clapper_app_queue_selection_new:
|
|
* @queue: (nullable): a #ClapperQueue
|
|
*
|
|
* Creates a new #ClapperAppQueueSelection instance.
|
|
*
|
|
* Returns: (transfer full): a new #ClapperAppQueueSelection.
|
|
*/
|
|
ClapperAppQueueSelection *
|
|
clapper_app_queue_selection_new (ClapperQueue *queue)
|
|
{
|
|
return g_object_new (CLAPPER_APP_TYPE_QUEUE_SELECTION, "queue", queue, NULL);
|
|
}
|
|
|
|
/*
|
|
* clapper_app_queue_selection_set_queue:
|
|
* @selection: a #ClapperAppQueueSelection
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Set #ClapperQueue to be managed by this selection model.
|
|
*/
|
|
void
|
|
clapper_app_queue_selection_set_queue (ClapperAppQueueSelection *self, ClapperQueue *queue)
|
|
{
|
|
guint n_before = 0, n_after = 0;
|
|
|
|
g_return_if_fail (CLAPPER_APP_IS_QUEUE_SELECTION (self));
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (queue));
|
|
|
|
if (self->queue) {
|
|
g_signal_handlers_disconnect_by_func (G_LIST_MODEL (self->queue), _queue_model_items_changed_cb, self);
|
|
g_signal_handlers_disconnect_by_func (self->queue, _queue_current_index_changed_cb, self);
|
|
|
|
n_before = clapper_queue_get_n_items (self->queue);
|
|
}
|
|
|
|
gst_object_replace ((GstObject **) &self->queue, GST_OBJECT_CAST (queue));
|
|
|
|
g_signal_connect (G_LIST_MODEL (self->queue), "items-changed",
|
|
G_CALLBACK (_queue_model_items_changed_cb), self);
|
|
g_signal_connect (self->queue, "notify::current-index",
|
|
G_CALLBACK (_queue_current_index_changed_cb), self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_QUEUE]);
|
|
|
|
n_after = clapper_queue_get_n_items (self->queue);
|
|
|
|
/* Refresh selected item after queue change */
|
|
self->current_position = GTK_INVALID_LIST_POSITION;
|
|
_queue_model_items_changed_cb (G_LIST_MODEL (self->queue), 0, n_before, n_after, self);
|
|
_refresh_current_selection (self);
|
|
}
|
|
|
|
/*
|
|
* clapper_app_queue_selection_get_queue:
|
|
* @selection: a #ClapperAppQueueSelection
|
|
*
|
|
* Get #ClapperQueue managed by this selection model.
|
|
*
|
|
* Returns: (transfer none): #ClapperQueue being managed.
|
|
*/
|
|
ClapperQueue *
|
|
clapper_app_queue_selection_get_queue (ClapperAppQueueSelection *self)
|
|
{
|
|
g_return_val_if_fail (CLAPPER_APP_IS_QUEUE_SELECTION (self), NULL);
|
|
|
|
return self->queue;
|
|
}
|
|
|
|
static void
|
|
clapper_app_queue_selection_init (ClapperAppQueueSelection *self)
|
|
{
|
|
self->current_position = GTK_INVALID_LIST_POSITION;
|
|
}
|
|
|
|
static void
|
|
clapper_app_queue_selection_finalize (GObject *object)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object);
|
|
|
|
if (self->queue) {
|
|
g_signal_handlers_disconnect_by_func (G_LIST_MODEL (self->queue), _queue_model_items_changed_cb, self);
|
|
g_signal_handlers_disconnect_by_func (self->queue, _queue_current_index_changed_cb, self);
|
|
|
|
g_object_unref (self->queue);
|
|
}
|
|
g_clear_object (&self->current_item);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
clapper_app_queue_selection_get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_QUEUE:
|
|
g_value_set_object (value, clapper_app_queue_selection_get_queue (self));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clapper_app_queue_selection_set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_QUEUE:
|
|
clapper_app_queue_selection_set_queue (self, g_value_get_object (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clapper_app_queue_selection_class_init (ClapperAppQueueSelectionClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappqueueselection", 0,
|
|
"Clapper App Queue Selection");
|
|
|
|
gobject_class->get_property = clapper_app_queue_selection_get_property;
|
|
gobject_class->set_property = clapper_app_queue_selection_set_property;
|
|
gobject_class->finalize = clapper_app_queue_selection_finalize;
|
|
|
|
/*
|
|
* ClapperAppQueueSelection:queue:
|
|
*
|
|
* The queue being managed.
|
|
*/
|
|
param_specs[PROP_QUEUE] = g_param_spec_object ("queue",
|
|
NULL, NULL, CLAPPER_TYPE_QUEUE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/*
|
|
* ClapperAppQueueSelection::item-selected:
|
|
* @selection: a #ClapperAppQueueSelection
|
|
* @index: an index of selected item
|
|
*
|
|
* Signals when user selected item within the [iface@Gtk.SelectionModel].
|
|
*
|
|
* Note that this signal is emitted only when item gets selected from
|
|
* the GTK side (also when the same item is reselected). If item was
|
|
* changed internally by e.g. progression of [class@Clapper.Queue],
|
|
* this signal will not be emitted.
|
|
*
|
|
* #ClapperAppQueueSelection automatically takes care of having its
|
|
* selection in sync with passed [class@Clapper.Queue], so you do not
|
|
* have to listen for the changes. This signal is useful if you need
|
|
* to differentiate what caused item selection, otherwise use either
|
|
* [signal@Gtk.SelectionModel::selection-changed] signal or listen for
|
|
* changes of [property@Clapper.Queue:current-item] property.
|
|
*/
|
|
signals[SIGNAL_ITEM_SELECTED] = g_signal_new ("item-selected",
|
|
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
|
|
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
|
|
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
|
}
|