mirror of
https://github.com/brl/mutter.git
synced 2024-11-22 16:10:41 -05:00
2007-09-27 Matthew Allum <mallum@openedhand.com>
* clutter/clutter-event.c: * clutter/clutter-event.h: * clutter/clutter-main.c: Further event tweaks; - Ref the event actor source - Protect against off stage events (button releases) - Move more into ClutterEventAny - Add a click count to button event (as yet unused) - Minor cleanups * clutter/clutter-actor.c: Make scale x/y a property. * clutter/clutter-private.h: Remove _clutter_actor_apply_modelview* * clutter/eglx/clutter-backend-egl.c: Warning cleanup * clutter/eglx/clutter-stage-egl.c: * clutter/glx/clutter-stage-glx.c: * clutter/sdl/clutter-stage-sdl.c: Avoid setting viewport directly, but set sync flag. * clutter/pango/pangoclutter-render.c: (draw_glyph): Minor cleanups. * clutter/Makefile.am: * tests/Makefile.am: * tests/test-score.c * clutter/clutter.h: * clutter/clutter-score.h: * clutter/clutter-score.c: Add very initial (broken) ClutterScore implementation.
This commit is contained in:
parent
bc7b1b3a16
commit
5ab0ed5a00
37
ChangeLog
37
ChangeLog
@ -1,3 +1,40 @@
|
||||
2007-09-27 Matthew Allum <mallum@openedhand.com>
|
||||
|
||||
* clutter/clutter-event.c:
|
||||
* clutter/clutter-event.h:
|
||||
* clutter/clutter-main.c:
|
||||
Further event tweaks;
|
||||
- Ref the event actor source
|
||||
- Protect against off stage events (button releases)
|
||||
- Move more into ClutterEventAny
|
||||
- Add a click count to button event (as yet unused)
|
||||
- Minor cleanups
|
||||
|
||||
* clutter/clutter-actor.c:
|
||||
Make scale x/y a property.
|
||||
|
||||
* clutter/clutter-private.h:
|
||||
Remove _clutter_actor_apply_modelview*
|
||||
|
||||
* clutter/eglx/clutter-backend-egl.c:
|
||||
Warning cleanup
|
||||
|
||||
* clutter/eglx/clutter-stage-egl.c:
|
||||
* clutter/glx/clutter-stage-glx.c:
|
||||
* clutter/sdl/clutter-stage-sdl.c:
|
||||
Avoid setting viewport directly, but set sync flag.
|
||||
|
||||
* clutter/pango/pangoclutter-render.c: (draw_glyph):
|
||||
Minor cleanups.
|
||||
|
||||
* clutter/Makefile.am:
|
||||
* tests/Makefile.am:
|
||||
* tests/test-score.c
|
||||
* clutter/clutter.h:
|
||||
* clutter/clutter-score.h:
|
||||
* clutter/clutter-score.c:
|
||||
Add very initial (broken) ClutterScore implementation.
|
||||
|
||||
2007-09-25 Ross Burton <ross@openedhand.com>
|
||||
|
||||
Merge from stable.
|
||||
|
@ -68,6 +68,7 @@ source_h = \
|
||||
$(srcdir)/clutter-stage.h \
|
||||
$(srcdir)/clutter-texture.h \
|
||||
$(srcdir)/clutter-timeline.h \
|
||||
$(srcdir)/clutter-score.h \
|
||||
$(srcdir)/clutter-timeout-pool.h \
|
||||
$(srcdir)/clutter-types.h \
|
||||
$(srcdir)/clutter-units.h \
|
||||
@ -151,6 +152,7 @@ source_c = \
|
||||
clutter-rectangle.c \
|
||||
clutter-texture.c \
|
||||
clutter-timeline.c \
|
||||
clutter-score.c \
|
||||
clutter-timeout-pool.c \
|
||||
clutter-util.c \
|
||||
clutter-vbox.c \
|
||||
|
@ -82,7 +82,9 @@ enum
|
||||
PROP_HAS_CLIP,
|
||||
PROP_OPACITY,
|
||||
PROP_NAME,
|
||||
PROP_VISIBLE
|
||||
PROP_VISIBLE,
|
||||
PROP_SCALE_X,
|
||||
PROP_SCALE_Y
|
||||
};
|
||||
|
||||
enum
|
||||
@ -106,6 +108,12 @@ enum
|
||||
|
||||
static guint actor_signals[LAST_SIGNAL] = { 0, };
|
||||
|
||||
static
|
||||
void _clutter_actor_apply_modelview_transform (ClutterActor * self);
|
||||
|
||||
static
|
||||
void _clutter_actor_apply_modelview_transform_recursive (ClutterActor * self);
|
||||
|
||||
static gboolean
|
||||
redraw_update_idle (gpointer data)
|
||||
{
|
||||
@ -397,7 +405,8 @@ clutter_actor_transform_point (ClutterActor *actor,
|
||||
* @vertex: The translated #ClutterVertex
|
||||
*
|
||||
* Transforms point in coordinates relative to the actor
|
||||
* into screen coordiances
|
||||
* into screen coordiances with the current actor tranform
|
||||
* (i.e. scale, rotation etc)
|
||||
*
|
||||
* Since: 0.4
|
||||
**/
|
||||
@ -582,7 +591,7 @@ clutter_actor_get_vertices (ClutterActor *self,
|
||||
* This function does not push/pop matrix; it is the responsibility
|
||||
* of the caller to do so as appropriate
|
||||
*/
|
||||
void
|
||||
static void
|
||||
_clutter_actor_apply_modelview_transform (ClutterActor * self)
|
||||
{
|
||||
ClutterActorPrivate *priv = self->priv;
|
||||
@ -640,7 +649,7 @@ _clutter_actor_apply_modelview_transform (ClutterActor * self)
|
||||
* This function does not push/pop matrix; it is the responsibility
|
||||
* of the caller to do so as appropriate
|
||||
*/
|
||||
void
|
||||
static void
|
||||
_clutter_actor_apply_modelview_transform_recursive (ClutterActor * self)
|
||||
{
|
||||
ClutterActor * parent;
|
||||
@ -878,6 +887,18 @@ clutter_actor_set_property (GObject *object,
|
||||
else
|
||||
clutter_actor_hide (actor);
|
||||
break;
|
||||
case PROP_SCALE_X:
|
||||
clutter_actor_set_scalex
|
||||
(actor,
|
||||
CLUTTER_FLOAT_TO_FIXED (g_value_get_double (value)),
|
||||
priv->scale_y);
|
||||
break;
|
||||
case PROP_SCALE_Y:
|
||||
clutter_actor_set_scalex
|
||||
(actor,
|
||||
priv->scale_x,
|
||||
CLUTTER_FLOAT_TO_FIXED (g_value_get_double (value)));
|
||||
break;
|
||||
case PROP_CLIP:
|
||||
{
|
||||
ClutterGeometry *geom = g_value_get_boxed (value);
|
||||
@ -935,6 +956,12 @@ clutter_actor_get_property (GObject *object,
|
||||
case PROP_CLIP:
|
||||
g_value_set_boxed (value, &(priv->clip));
|
||||
break;
|
||||
case PROP_SCALE_X:
|
||||
g_value_set_double (value, CLUTTER_FIXED_TO_DOUBLE (priv->scale_x));
|
||||
break;
|
||||
case PROP_SCALE_Y:
|
||||
g_value_set_double (value, CLUTTER_FIXED_TO_DOUBLE (priv->scale_y));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -1100,6 +1127,43 @@ clutter_actor_class_init (ClutterActorClass *klass)
|
||||
NULL,
|
||||
CLUTTER_PARAM_READWRITE));
|
||||
|
||||
/**
|
||||
* ClutterActor::scale-x:
|
||||
*
|
||||
* The horizontal scale of the actor
|
||||
*
|
||||
* Since: 0.6
|
||||
*/
|
||||
g_object_class_install_property
|
||||
(object_class,
|
||||
PROP_SCALE_X,
|
||||
g_param_spec_double ("scale-x",
|
||||
"Scale-X",
|
||||
"Scale X",
|
||||
0.0,
|
||||
G_MAXDOUBLE,
|
||||
1.0,
|
||||
CLUTTER_PARAM_READWRITE));
|
||||
|
||||
/**
|
||||
* ClutterActor::scale-y:
|
||||
*
|
||||
* The vertical scale of the actor
|
||||
*
|
||||
* Since: 0.6
|
||||
*/
|
||||
g_object_class_install_property
|
||||
(object_class,
|
||||
PROP_SCALE_Y,
|
||||
g_param_spec_double ("scale-y",
|
||||
"Scale-Y",
|
||||
"Scale Y",
|
||||
0.0,
|
||||
G_MAXDOUBLE,
|
||||
1.0,
|
||||
CLUTTER_PARAM_READWRITE));
|
||||
|
||||
|
||||
/**
|
||||
* ClutterActor::destroy:
|
||||
* @actor: the object which received the signal
|
||||
@ -1887,6 +1951,15 @@ clutter_actor_set_scalex (ClutterActor *self,
|
||||
self->priv->scale_x = scale_x;
|
||||
self->priv->scale_y = scale_y;
|
||||
|
||||
g_object_ref (self);
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "scale-x");
|
||||
g_object_notify (G_OBJECT (self), "scale-y");
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
g_object_unref (self);
|
||||
|
||||
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
||||
clutter_actor_queue_redraw (self);
|
||||
}
|
||||
|
@ -404,6 +404,11 @@ clutter_event_free (ClutterEvent *event)
|
||||
{
|
||||
if (G_LIKELY (event))
|
||||
{
|
||||
ClutterActor *source = NULL;
|
||||
|
||||
source = clutter_event_get_source (event);
|
||||
if (source)
|
||||
g_object_unref (source);
|
||||
g_slice_free (ClutterEvent, event);
|
||||
}
|
||||
}
|
||||
@ -422,7 +427,6 @@ ClutterEvent *
|
||||
clutter_event_get (void)
|
||||
{
|
||||
ClutterMainContext *context = clutter_context_get_default ();
|
||||
GList *item;
|
||||
|
||||
if (!context->events_queue)
|
||||
return NULL;
|
||||
|
@ -51,6 +51,10 @@ typedef enum {
|
||||
CLUTTER_BUTTON5_MASK = 1 << 12
|
||||
} ClutterModifierType;
|
||||
|
||||
typedef enum {
|
||||
CLUTTER_EVENT_FLAG_COOKED = 1 << 0,
|
||||
} ClutterEventFlags;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CLUTTER_NOTHING = 0,
|
||||
@ -100,12 +104,15 @@ typedef struct _ClutterInputDevice ClutterInputDevice;
|
||||
struct _ClutterAnyEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
};
|
||||
|
||||
struct _ClutterKeyEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
ClutterModifierType modifier_state;
|
||||
guint keyval;
|
||||
guint16 hardware_keycode;
|
||||
@ -116,10 +123,12 @@ struct _ClutterButtonEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
gint x;
|
||||
gint y;
|
||||
ClutterModifierType modifier_state;
|
||||
guint32 button;
|
||||
guint click_count;
|
||||
gdouble *axes; /* Future use */
|
||||
ClutterInputDevice *device; /* Future use */
|
||||
ClutterActor *source;
|
||||
@ -129,6 +138,7 @@ struct _ClutterMotionEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
gint x;
|
||||
gint y;
|
||||
ClutterModifierType modifier_state;
|
||||
@ -141,6 +151,7 @@ struct _ClutterScrollEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
gint x;
|
||||
gint y;
|
||||
ClutterScrollDirection direction;
|
||||
@ -153,6 +164,8 @@ struct _ClutterScrollEvent
|
||||
struct _ClutterStageStateEvent
|
||||
{
|
||||
ClutterEventType type;
|
||||
guint32 time;
|
||||
ClutterEventFlags flags;
|
||||
ClutterStageState changed_mask;
|
||||
ClutterStageState new_state;
|
||||
};
|
||||
|
@ -406,8 +406,8 @@ sort_z_order (gconstpointer a,
|
||||
{
|
||||
int depth_a, depth_b;
|
||||
|
||||
depth_a = clutter_actor_get_depth (a);
|
||||
depth_b = clutter_actor_get_depth (b);
|
||||
depth_a = clutter_actor_get_depth (CLUTTER_ACTOR(a));
|
||||
depth_b = clutter_actor_get_depth (CLUTTER_ACTOR(b));
|
||||
|
||||
if (depth_a == depth_b)
|
||||
return 0;
|
||||
|
@ -148,7 +148,7 @@ clutter_redraw (void)
|
||||
CLUTTER_UNSET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_SYNC_MATRICES);
|
||||
}
|
||||
|
||||
/* Call through ti the actual backend to do the painting down from
|
||||
/* Call through to the actual backend to do the painting down from
|
||||
* the stage. It will likely need to swap buffers, vblank sync etc
|
||||
* which will be windowing system dependant.
|
||||
*/
|
||||
@ -200,6 +200,10 @@ clutter_get_motion_events_enabled (void)
|
||||
void
|
||||
clutter_do_event (ClutterEvent *event)
|
||||
{
|
||||
/* FIXME: This should probably be clutter_cook_event() - it would
|
||||
* take a raw event from the backend and 'cook' it so its more tasty.
|
||||
*
|
||||
*/
|
||||
ClutterMainContext *context;
|
||||
ClutterBackend *backend;
|
||||
ClutterActor *stage;
|
||||
@ -207,16 +211,13 @@ clutter_do_event (ClutterEvent *event)
|
||||
|
||||
context = clutter_context_get_default ();
|
||||
backend = context->backend;
|
||||
stage = _clutter_backend_get_stage (backend);
|
||||
stage = _clutter_backend_get_stage (backend);
|
||||
|
||||
if (!stage)
|
||||
return;
|
||||
|
||||
CLUTTER_TIMESTAMP (EVENT, "Event received");
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
*/
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case CLUTTER_NOTHING:
|
||||
@ -224,7 +225,6 @@ clutter_do_event (ClutterEvent *event)
|
||||
|
||||
case CLUTTER_DESTROY_NOTIFY:
|
||||
case CLUTTER_DELETE:
|
||||
/* FIXME: handle delete working in stage */
|
||||
if (clutter_stage_event (CLUTTER_STAGE (stage), event))
|
||||
clutter_main_quit ();
|
||||
break;
|
||||
@ -237,8 +237,7 @@ clutter_do_event (ClutterEvent *event)
|
||||
|
||||
g_return_if_fail (actor != NULL);
|
||||
|
||||
/* FIXME: should we ref ? */
|
||||
event->key.source = actor;
|
||||
event->key.source = g_object_ref(actor);
|
||||
|
||||
/* bubble up */
|
||||
do
|
||||
@ -253,7 +252,7 @@ clutter_do_event (ClutterEvent *event)
|
||||
if (context->motion_events_per_actor == FALSE)
|
||||
{
|
||||
/* Only stage gets motion events */
|
||||
event->motion.source = stage;
|
||||
event->motion.source = g_object_ref(stage);
|
||||
clutter_actor_event (stage, event);
|
||||
break;
|
||||
}
|
||||
@ -268,6 +267,15 @@ clutter_do_event (ClutterEvent *event)
|
||||
|
||||
clutter_event_get_coords (event, &x, &y);
|
||||
|
||||
/* Safety on - probably a release off stage ?
|
||||
* FIXME: should likely deliver the release somehow - grabs ?
|
||||
*/
|
||||
if (x > CLUTTER_STAGE_WIDTH()
|
||||
|| y > CLUTTER_STAGE_HEIGHT()
|
||||
|| x < 0
|
||||
|| y < 0)
|
||||
break;
|
||||
|
||||
/* Map the event to a reactive actor */
|
||||
actor = _clutter_do_pick (CLUTTER_STAGE (stage),
|
||||
x, y,
|
||||
@ -277,9 +285,9 @@ clutter_do_event (ClutterEvent *event)
|
||||
x, y, actor);
|
||||
|
||||
if (event->type == CLUTTER_SCROLL)
|
||||
event->scroll.source = actor;
|
||||
event->scroll.source = g_object_ref(actor);
|
||||
else
|
||||
event->button.source = actor;
|
||||
event->button.source = g_object_ref(actor);
|
||||
|
||||
/* Motion enter leave events */
|
||||
if (event->type == CLUTTER_MOTION)
|
||||
@ -300,6 +308,8 @@ clutter_do_event (ClutterEvent *event)
|
||||
* FIXME: for an optimisation should check if there are
|
||||
* actually any reactive actors and avoid the pick all togeather
|
||||
* (signalling just the stage). Should be big help for gles.
|
||||
*
|
||||
* FIXME: Actors be able to stop emission.
|
||||
*/
|
||||
while (actor)
|
||||
{
|
||||
|
@ -137,10 +137,6 @@ ClutterActor* _clutter_do_pick (ClutterStage *stage,
|
||||
/* Does this need to be private ? */
|
||||
void clutter_do_event (ClutterEvent *event);
|
||||
|
||||
void _clutter_actor_apply_modelview_transform (ClutterActor * self);
|
||||
|
||||
void _clutter_actor_apply_modelview_transform_recursive (ClutterActor * self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* _HAVE_CLUTTER_PRIVATE_H */
|
||||
|
501
clutter/clutter-score.c
Normal file
501
clutter/clutter-score.c
Normal file
@ -0,0 +1,501 @@
|
||||
/*
|
||||
* Clutter.
|
||||
*
|
||||
* An OpenGL based 'interactive canvas' library.
|
||||
*
|
||||
* Authored By Matthew Allum <mallum@openedhand.com>
|
||||
*
|
||||
* Copyright (C) 2007 OpenedHand
|
||||
*
|
||||
* 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 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* IDEAS:
|
||||
* API;
|
||||
* - add()
|
||||
* + an new timeline to beginning of score
|
||||
* - append (timeline_existing, timeline_new, delay)
|
||||
* + appends a new timeline to an existing one
|
||||
*
|
||||
* ScoreEntry
|
||||
* {
|
||||
* Timeline *base;
|
||||
* GList *next_timelines; - to start on completion of base,
|
||||
* (points to score entrys)
|
||||
* Callback id;
|
||||
* delay
|
||||
* }
|
||||
*
|
||||
* start()/stop(),remove(),remove_all() ?
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:clutter-score @short_description: A class for sequencing
|
||||
* multiple #ClutterTimelines in order
|
||||
*
|
||||
* #ClutterScore is a base class for sequencing multiple timelines in order.
|
||||
*/
|
||||
|
||||
#ifndef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "clutter-score.h"
|
||||
#include "clutter-main.h"
|
||||
#include "clutter-marshal.h"
|
||||
#include "clutter-private.h"
|
||||
#include "clutter-debug.h"
|
||||
|
||||
G_DEFINE_TYPE (ClutterScore, clutter_score, G_TYPE_OBJECT);
|
||||
|
||||
typedef struct ClutterScoreEntry
|
||||
{
|
||||
ClutterTimeline *timeline;
|
||||
gulong handler_id;
|
||||
GSList *child_entries;
|
||||
ClutterScore *score;
|
||||
}
|
||||
ClutterScoreEntry;
|
||||
|
||||
struct _ClutterScorePrivate
|
||||
{
|
||||
GSList *entries;
|
||||
GHashTable *running_timelines;
|
||||
guint loop : 1;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LOOP
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
NEW_TIMELINE,
|
||||
STARTED,
|
||||
PAUSED,
|
||||
COMPLETED,
|
||||
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static int score_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void start_entry (ClutterScoreEntry *entry);
|
||||
|
||||
/* Object */
|
||||
|
||||
static void
|
||||
clutter_score_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
ClutterScore *score;
|
||||
ClutterScorePrivate *priv;
|
||||
|
||||
score = CLUTTER_SCORE(object);
|
||||
priv = score->priv;
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_LOOP:
|
||||
priv->loop = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clutter_score_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
ClutterScore *score;
|
||||
ClutterScorePrivate *priv;
|
||||
|
||||
score = CLUTTER_SCORE(object);
|
||||
priv = score->priv;
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_LOOP:
|
||||
g_value_set_boolean (value, priv->loop);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clutter_score_finalize (GObject *object)
|
||||
{
|
||||
G_OBJECT_CLASS (clutter_score_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clutter_score_dispose (GObject *object)
|
||||
{
|
||||
ClutterScore *self = CLUTTER_SCORE(object);
|
||||
ClutterScorePrivate *priv;
|
||||
|
||||
priv = self->priv;
|
||||
|
||||
if (priv != NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (clutter_score_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
clutter_score_class_init (ClutterScoreClass *klass)
|
||||
{
|
||||
GObjectClass *object_class;
|
||||
|
||||
object_class = (GObjectClass*) klass;
|
||||
|
||||
object_class->set_property = clutter_score_set_property;
|
||||
object_class->get_property = clutter_score_get_property;
|
||||
object_class->finalize = clutter_score_finalize;
|
||||
object_class->dispose = clutter_score_dispose;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (ClutterScorePrivate));
|
||||
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* ClutterScore::new-frame:
|
||||
* @score: the score which received the signal
|
||||
* @timeline: the number of the new frame
|
||||
*
|
||||
* The ::new-timeline signal is emitted each time a new timeline in the
|
||||
* score is reached.
|
||||
*/
|
||||
score_signals[NEW_TIMELINE] =
|
||||
g_signal_new ("new-timeline",
|
||||
G_TYPE_FROM_CLASS (object_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (ClutterScoreClass, new_frame),
|
||||
NULL, NULL,
|
||||
clutter_marshal_VOID__OBJECT,
|
||||
G_TYPE_NONE,
|
||||
1, CLUTTER_TYPE_TIMELINE);
|
||||
score_signals[COMPLETED] =
|
||||
g_signal_new ("completed",
|
||||
G_TYPE_FROM_CLASS (object_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (ClutterScoreClass, completed),
|
||||
NULL, NULL,
|
||||
clutter_marshal_VOID__VOID,
|
||||
G_TYPE_NONE, 0);
|
||||
score_signals[STARTED] =
|
||||
g_signal_new ("started",
|
||||
G_TYPE_FROM_CLASS (object_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (ClutterScoreClass, started),
|
||||
NULL, NULL,
|
||||
clutter_marshal_VOID__VOID,
|
||||
G_TYPE_NONE, 0);
|
||||
score_signals[PAUSED] =
|
||||
g_signal_new ("paused",
|
||||
G_TYPE_FROM_CLASS (object_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (ClutterScoreClass, paused),
|
||||
NULL, NULL,
|
||||
clutter_marshal_VOID__VOID,
|
||||
G_TYPE_NONE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
clutter_score_init (ClutterScore *self)
|
||||
{
|
||||
self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
|
||||
CLUTTER_TYPE_SCORE,
|
||||
ClutterScorePrivate);
|
||||
|
||||
self->priv->running_timelines = g_hash_table_new(NULL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_set_loop:
|
||||
* @score: a #ClutterScore
|
||||
* @loop: %TRUE for enable looping
|
||||
*
|
||||
* Sets whether @score should loop.
|
||||
*/
|
||||
void
|
||||
clutter_score_set_loop (ClutterScore *score,
|
||||
gboolean loop)
|
||||
{
|
||||
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
||||
|
||||
if (score->priv->loop != loop)
|
||||
{
|
||||
g_object_ref (score);
|
||||
|
||||
score->priv->loop = loop;
|
||||
|
||||
g_object_notify (G_OBJECT (score), "loop");
|
||||
g_object_unref (score);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_get_loop:
|
||||
* @score: a #ClutterScore
|
||||
*
|
||||
* Gets whether @score is looping
|
||||
*
|
||||
* Return value: %TRUE if the score is looping
|
||||
*/
|
||||
gboolean
|
||||
clutter_score_get_loop (ClutterScore *score)
|
||||
{
|
||||
g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE);
|
||||
|
||||
return score->priv->loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_rewind:
|
||||
* @score: A #ClutterScore
|
||||
*
|
||||
* Rewinds #ClutterScore to frame 0.
|
||||
**/
|
||||
void
|
||||
clutter_score_rewind (ClutterScore *score)
|
||||
{
|
||||
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_is_playing:
|
||||
* @score: A #ClutterScore
|
||||
*
|
||||
* Query state of a #ClutterScore instance.
|
||||
*
|
||||
* Return Value: TRUE if score is currently playing, FALSE if not.
|
||||
*/
|
||||
gboolean
|
||||
clutter_score_is_playing (ClutterScore *score)
|
||||
{
|
||||
g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE);
|
||||
|
||||
return !!g_hash_table_size(score->priv->running_timelines);
|
||||
}
|
||||
|
||||
static void
|
||||
on_timeline_finish (ClutterTimeline *timeline,
|
||||
ClutterScoreEntry *entry)
|
||||
{
|
||||
GSList *item;
|
||||
|
||||
g_hash_table_remove (entry->score->priv->running_timelines,
|
||||
GINT_TO_POINTER(entry->handler_id));
|
||||
g_signal_handler_disconnect (timeline, entry->handler_id);
|
||||
|
||||
printf("completed %li\n", entry->handler_id);
|
||||
|
||||
for (item = entry->child_entries; item != NULL; item = item->next)
|
||||
{
|
||||
ClutterScoreEntry *child_entry = item->data;
|
||||
start_entry (child_entry);
|
||||
}
|
||||
|
||||
if (clutter_score_is_playing (entry->score) == FALSE)
|
||||
{
|
||||
/* Score has finished - fire 'completed' signal */
|
||||
/* Also check if looped etc */
|
||||
printf("looks like we finished\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_entry (ClutterScoreEntry *entry)
|
||||
{
|
||||
entry->handler_id = g_signal_connect (entry->timeline,
|
||||
"completed",
|
||||
G_CALLBACK (on_timeline_finish),
|
||||
entry);
|
||||
|
||||
printf("started %li\n", entry->handler_id);
|
||||
|
||||
g_hash_table_insert (entry->score->priv->running_timelines,
|
||||
GINT_TO_POINTER(entry->handler_id),
|
||||
entry);
|
||||
|
||||
clutter_timeline_start (entry->timeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_start:
|
||||
* @score: A #ClutterScore
|
||||
*
|
||||
* Query state of a #ClutterScore instance.
|
||||
*
|
||||
* Return Value: TRUE if score is currently playing, FALSE if not.
|
||||
*/
|
||||
void
|
||||
clutter_score_start (ClutterScore *score)
|
||||
{
|
||||
GSList *item;
|
||||
ClutterScorePrivate *priv;
|
||||
|
||||
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
||||
|
||||
priv = score->priv;
|
||||
|
||||
for (item = priv->entries; item != NULL; item = item->next)
|
||||
{
|
||||
ClutterScoreEntry *entry = item->data;
|
||||
|
||||
start_entry (entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_start:
|
||||
* @score: A #ClutterScore
|
||||
*
|
||||
* Query state of a #ClutterScore instance.
|
||||
*
|
||||
* Return Value: TRUE if score is currently playing, FALSE if not.
|
||||
*/
|
||||
void
|
||||
clutter_score_stop (ClutterScore *score)
|
||||
{
|
||||
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
||||
|
||||
/* foreach hash / pause */
|
||||
}
|
||||
|
||||
static ClutterScoreEntry*
|
||||
find_entry (GSList *list, ClutterTimeline *timeline)
|
||||
{
|
||||
GSList *item;
|
||||
ClutterScoreEntry *res = NULL;
|
||||
|
||||
if (list == NULL)
|
||||
return NULL;
|
||||
|
||||
for (item = list; item != NULL && res == NULL; item = item->next)
|
||||
{
|
||||
ClutterScoreEntry *entry = item->data;
|
||||
|
||||
g_assert (entry != NULL);
|
||||
|
||||
if (entry->timeline == timeline)
|
||||
return entry;
|
||||
|
||||
if (entry->child_entries)
|
||||
res = find_entry (entry->child_entries, timeline);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_append:
|
||||
* @score: A #ClutterScore
|
||||
* @timeline_existing: A #ClutterTimeline in the score
|
||||
* @timeline_new: A new #ClutterTimeline to start when #timeline_existing has
|
||||
* completed,
|
||||
*
|
||||
* Appends a new timeline to an one existing in the score.
|
||||
*
|
||||
*/
|
||||
void
|
||||
clutter_score_append (ClutterScore *score,
|
||||
ClutterTimeline *timeline_existing,
|
||||
ClutterTimeline *timeline_new)
|
||||
{
|
||||
ClutterScorePrivate *priv;
|
||||
ClutterScoreEntry *entry, *entry_new;
|
||||
|
||||
priv = score->priv;
|
||||
|
||||
/* Appends a timeline to the end of another */
|
||||
if ((entry = find_entry (priv->entries, timeline_existing)) != NULL)
|
||||
{
|
||||
entry_new = g_new0(ClutterScoreEntry, 1);
|
||||
entry->timeline = g_object_ref (timeline_new);
|
||||
entry->score = score;
|
||||
entry->child_entries = g_slist_append (entry->child_entries, entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clutter_score_add:
|
||||
* @score: A #ClutterScore
|
||||
* @timeline: A #ClutterTimeline
|
||||
*
|
||||
* Adds a new initial timeline to start when the score is started.
|
||||
*
|
||||
*/
|
||||
void
|
||||
clutter_score_add (ClutterScore *score,
|
||||
ClutterTimeline *timeline)
|
||||
{
|
||||
ClutterScorePrivate *priv;
|
||||
ClutterScoreEntry *entry;
|
||||
|
||||
priv = score->priv;
|
||||
|
||||
/* Added timelines are always started first */
|
||||
entry = g_new0(ClutterScoreEntry, 1);
|
||||
entry->timeline = g_object_ref (timeline);
|
||||
entry->score = score;
|
||||
score->priv->entries = g_slist_append (score->priv->entries, entry);
|
||||
}
|
||||
|
||||
void
|
||||
clutter_score_remove (ClutterScore *score,
|
||||
ClutterTimeline *timeline_parent,
|
||||
ClutterTimeline *timeline)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
clutter_score_remove_all (ClutterScore *score)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* clutter_score_new:
|
||||
*
|
||||
* Create a new #ClutterScore instance.
|
||||
*
|
||||
* Return Value: a new #ClutterScore
|
||||
*/
|
||||
ClutterScore*
|
||||
clutter_score_new ()
|
||||
{
|
||||
return g_object_new (CLUTTER_TYPE_SCORE, NULL);
|
||||
}
|
126
clutter/clutter-score.h
Normal file
126
clutter/clutter-score.h
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Clutter.
|
||||
*
|
||||
* An OpenGL based 'interactive canvas' library.
|
||||
*
|
||||
* Authored By Matthew Allum <mallum@openedhand.com>
|
||||
*
|
||||
* Copyright (C) 2006 OpenedHand
|
||||
*
|
||||
* 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 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef _HAVE_CLUTTER_SCORE_H
|
||||
#define _HAVE_CLUTTER_SCORE_H
|
||||
|
||||
/* clutter-score.h */
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <clutter/clutter-timeline.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLUTTER_TYPE_SCORE clutter_score_get_type()
|
||||
|
||||
#define CLUTTER_SCORE(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
|
||||
CLUTTER_TYPE_SCORE, ClutterScore))
|
||||
|
||||
#define CLUTTER_SCORE_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST ((klass), \
|
||||
CLUTTER_TYPE_SCORE, ClutterScoreClass))
|
||||
|
||||
#define CLUTTER_IS_SCORE(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
|
||||
CLUTTER_TYPE_SCORE))
|
||||
|
||||
#define CLUTTER_IS_SCORE_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
|
||||
CLUTTER_TYPE_SCORE))
|
||||
|
||||
#define CLUTTER_SCORE_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
|
||||
CLUTTER_TYPE_SCORE, ClutterScoreClass))
|
||||
|
||||
typedef struct _ClutterScore ClutterScore;
|
||||
typedef struct _ClutterScoreClass ClutterScoreClass;
|
||||
typedef struct _ClutterScorePrivate ClutterScorePrivate;
|
||||
|
||||
struct _ClutterScore
|
||||
{
|
||||
/*< private >*/
|
||||
GObject parent;
|
||||
ClutterScorePrivate *priv;
|
||||
};
|
||||
|
||||
struct _ClutterScoreClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (*started) (ClutterScore *score);
|
||||
void (*completed) (ClutterScore *score);
|
||||
void (*paused) (ClutterScore *score);
|
||||
|
||||
void (*_clutter_score_1) (void);
|
||||
void (*_clutter_score_2) (void);
|
||||
void (*_clutter_score_3) (void);
|
||||
void (*_clutter_score_4) (void);
|
||||
void (*_clutter_score_5) (void);
|
||||
};
|
||||
|
||||
GType clutter_score_get_type (void) G_GNUC_CONST;
|
||||
|
||||
ClutterScore *clutter_score_new (void);
|
||||
|
||||
void
|
||||
clutter_score_set_loop (ClutterScore *score,
|
||||
gboolean loop);
|
||||
|
||||
gboolean
|
||||
clutter_score_get_loop (ClutterScore *score);
|
||||
|
||||
void
|
||||
clutter_score_rewind (ClutterScore *score);
|
||||
|
||||
gboolean
|
||||
clutter_score_is_playing (ClutterScore *score);
|
||||
void
|
||||
clutter_score_start (ClutterScore *score);
|
||||
|
||||
void
|
||||
clutter_score_stop (ClutterScore *score);
|
||||
|
||||
void
|
||||
clutter_score_append (ClutterScore *score,
|
||||
ClutterTimeline *timeline_existing,
|
||||
ClutterTimeline *timeline_new);
|
||||
|
||||
void
|
||||
clutter_score_add (ClutterScore *score,
|
||||
ClutterTimeline *timeline);
|
||||
|
||||
void
|
||||
clutter_score_remove (ClutterScore *score,
|
||||
ClutterTimeline *timeline_parent,
|
||||
ClutterTimeline *timeline);
|
||||
|
||||
void
|
||||
clutter_score_remove_all (ClutterScore *score);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
@ -57,6 +57,7 @@
|
||||
#include "clutter-texture.h"
|
||||
#include "clutter-timeout-pool.h"
|
||||
#include "clutter-timeline.h"
|
||||
#include "clutter-score.h"
|
||||
#include "clutter-types.h"
|
||||
#include "clutter-units.h"
|
||||
#include "clutter-util.h"
|
||||
|
@ -76,7 +76,7 @@ clutter_backend_egl_post_parse (ClutterBackend *backend,
|
||||
|
||||
backend_egl->display_name = g_strdup (clutter_display_name);
|
||||
|
||||
backend_egl->edpy = eglGetDisplay(backend_egl->xdpy);
|
||||
backend_egl->edpy = eglGetDisplay((NativeDisplayType)backend_egl->xdpy);
|
||||
|
||||
dpi = (((double) DisplayHeight (backend_egl->xdpy, backend_egl->xscreen_num) * 25.4)
|
||||
/ (double) DisplayHeightMM (backend_egl->xdpy, backend_egl->xscreen_num));
|
||||
|
@ -89,7 +89,6 @@ clutter_stage_egl_realize (ClutterActor *actor)
|
||||
EGLConfig configs[2];
|
||||
EGLint config_count;
|
||||
EGLBoolean status;
|
||||
ClutterPerspective perspective;
|
||||
|
||||
gboolean is_offscreen;
|
||||
|
||||
@ -183,13 +182,7 @@ clutter_stage_egl_realize (ClutterActor *actor)
|
||||
/* FIXME */
|
||||
}
|
||||
|
||||
clutter_stage_get_perspectivex (CLUTTER_STAGE (actor), &perspective);
|
||||
cogl_setup_viewport (clutter_actor_get_width (actor),
|
||||
clutter_actor_get_height (actor),
|
||||
perspective.fovy,
|
||||
perspective.aspect,
|
||||
perspective.z_near,
|
||||
perspective.z_far);
|
||||
CLUTTER_SET_PRIVATE_FLAGS(actor, CLUTTER_ACTOR_SYNC_MATRICES);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -392,14 +392,8 @@ clutter_stage_glx_realize (ClutterActor *actor)
|
||||
|
||||
}
|
||||
|
||||
clutter_stage_get_perspectivex (CLUTTER_STAGE (actor), &perspective);
|
||||
cogl_setup_viewport (clutter_actor_get_width (actor),
|
||||
clutter_actor_get_height (actor),
|
||||
perspective.fovy,
|
||||
perspective.aspect,
|
||||
perspective.z_near,
|
||||
perspective.z_far);
|
||||
|
||||
/* Make sure the viewport gets set up correctly */
|
||||
CLUTTER_SET_PRIVATE_FLAGS(actor, CLUTTER_ACTOR_SYNC_MATRICES);
|
||||
return;
|
||||
|
||||
fail:
|
||||
|
@ -58,7 +58,7 @@ typedef struct tc_slice {
|
||||
int avail, y;
|
||||
} tc_slice;
|
||||
|
||||
static int tc_generation;
|
||||
static int tc_generation = 0;
|
||||
static tc_slice slices[TC_HEIGHT / TC_ROUND];
|
||||
static tc_texture *first_texture;
|
||||
|
||||
@ -339,11 +339,6 @@ draw_glyph (PangoRenderer *renderer_,
|
||||
_pango_clutter_font_set_cache_glyph_data (font, glyph, g);
|
||||
}
|
||||
|
||||
/*
|
||||
if (renderer->curtex)
|
||||
glEnd ();
|
||||
*/
|
||||
|
||||
tc_get (&g->tex, bm.width, bm.height);
|
||||
|
||||
g->left = bm.left;
|
||||
@ -365,8 +360,9 @@ draw_glyph (PangoRenderer *renderer_,
|
||||
CGL_UNSIGNED_BYTE,
|
||||
bm.bitmap);
|
||||
|
||||
glTexParameteri (CGL_TEXTURE_2D, GL_GENERATE_MIPMAP, FALSE);
|
||||
|
||||
renderer->curtex = g->tex.name;
|
||||
/* glBegin (GL_QUADS); */
|
||||
}
|
||||
else CLUTTER_NOTE (PANGO, g_message ("cache succsess %i\n", glyph));
|
||||
|
||||
@ -380,15 +376,8 @@ draw_glyph (PangoRenderer *renderer_,
|
||||
|
||||
if (g->tex.name != renderer->curtex)
|
||||
{
|
||||
/*
|
||||
if (renderer->curtex)
|
||||
glEnd ();
|
||||
*/
|
||||
|
||||
cogl_texture_bind (CGL_TEXTURE_2D, g->tex.name);
|
||||
renderer->curtex = g->tex.name;
|
||||
|
||||
/* glBegin (GL_QUADS); */
|
||||
}
|
||||
|
||||
cogl_texture_quad (x,
|
||||
|
@ -78,13 +78,7 @@ clutter_stage_sdl_realize (ClutterActor *actor)
|
||||
return;
|
||||
}
|
||||
|
||||
clutter_stage_get_perspectivex (CLUTTER_STAGE (actor), &perspective);
|
||||
cogl_setup_viewport (clutter_actor_get_width (actor),
|
||||
clutter_actor_get_height (actor),
|
||||
perspective.fovy,
|
||||
perspective.aspect,
|
||||
perspective.z_near,
|
||||
perspective.z_far);
|
||||
CLUTTER_SET_PRIVATE_FLAGS(actor, CLUTTER_ACTOR_SYNC_MATRICES);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1,7 +1,7 @@
|
||||
noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \
|
||||
test-actors test-behave test-text test-entry test-project \
|
||||
test-boxes test-perspective test-rotate test-depth \
|
||||
test-threads test-timeline
|
||||
test-threads test-timeline test-score
|
||||
|
||||
INCLUDES = -I$(top_srcdir)/
|
||||
LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la
|
||||
@ -23,5 +23,6 @@ test_rotate_SOURCES = test-rotate.c
|
||||
test_depth_SOURCES = test-depth.c
|
||||
test_threads_SOURCES = test-threads.c
|
||||
test_timeline_SOURCES = test-timeline.c
|
||||
test_score_SOURCES = test-score.c
|
||||
|
||||
EXTRA_DIST = redhand.png
|
||||
|
36
tests/test-score.c
Normal file
36
tests/test-score.c
Normal file
@ -0,0 +1,36 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <clutter/clutter.h>
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
ClutterScore *score;
|
||||
ClutterTimeline *timeline_1;
|
||||
ClutterTimeline *timeline_2;
|
||||
ClutterTimeline *timeline_3;
|
||||
|
||||
clutter_init (&argc, &argv);
|
||||
|
||||
timeline_1 = clutter_timeline_new (10, 120);
|
||||
timeline_2 = clutter_timeline_clone (timeline_1);
|
||||
timeline_3 = clutter_timeline_clone (timeline_1);
|
||||
|
||||
score = clutter_score_new();
|
||||
clutter_score_add (score, timeline_1);
|
||||
clutter_score_append (score, timeline_1, timeline_2);
|
||||
#if 0
|
||||
clutter_score_append (score, timeline_2, timeline_3);
|
||||
#endif
|
||||
clutter_score_start (score);
|
||||
|
||||
clutter_main ();
|
||||
|
||||
g_object_unref (score);
|
||||
g_object_unref (timeline_1);
|
||||
g_object_unref (timeline_2);
|
||||
g_object_unref (timeline_3);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue
Block a user