Add operations
Allow setting the type of segments to move, line or curve, from the context menu, and do the necessary adjustments to maintain smoothness, in particular between curve and line segments.
This commit is contained in:
433
tests/curve.c
433
tests/curve.c
@@ -1,12 +1,94 @@
|
||||
/* TODO
|
||||
* - point insert/remove
|
||||
* - rename to CurveEditor
|
||||
* - add properties
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
/* Set q to the projection of p onto the line through a and b */
|
||||
static void
|
||||
closest_point (const graphene_point_t *p,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b,
|
||||
graphene_point_t *q)
|
||||
{
|
||||
graphene_vec2_t n;
|
||||
graphene_vec2_t ap;
|
||||
float t;
|
||||
|
||||
graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
|
||||
graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
|
||||
|
||||
t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
|
||||
|
||||
q->x = a->x + t * (b->x - a->x);
|
||||
q->y = a->y + t * (b->y - a->y);
|
||||
}
|
||||
|
||||
/* Set q to the point on the line through p and a that is
|
||||
* at a distance of d from p, on the opposite side
|
||||
*/
|
||||
static void
|
||||
opposite_point (const graphene_point_t *p,
|
||||
const graphene_point_t *a,
|
||||
float d,
|
||||
graphene_point_t *q)
|
||||
{
|
||||
graphene_vec2_t ap;
|
||||
float t;
|
||||
|
||||
graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
|
||||
|
||||
t = - sqrt (d * d / graphene_vec2_dot (&ap, &ap));
|
||||
|
||||
q->x = p->x + t * (a->x - p->x);
|
||||
q->y = p->y + t * (a->y - p->y);
|
||||
}
|
||||
|
||||
#define RADIUS 5
|
||||
|
||||
G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MOVE,
|
||||
LINE,
|
||||
CURVE
|
||||
} Operation;
|
||||
|
||||
static const char *
|
||||
op_to_string (Operation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case MOVE:
|
||||
return "move";
|
||||
case LINE:
|
||||
return "line";
|
||||
case CURVE:
|
||||
return "curve";
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static Operation
|
||||
op_from_string (const char *s)
|
||||
{
|
||||
if (strcmp (s, "move") == 0)
|
||||
return MOVE;
|
||||
else if (strcmp (s, "line") == 0)
|
||||
return LINE;
|
||||
else if (strcmp (s, "curve") == 0)
|
||||
return CURVE;
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Operation op;
|
||||
gboolean edit;
|
||||
gboolean smooth;
|
||||
} PointData;
|
||||
@@ -76,6 +158,7 @@ drag_update (GtkGestureDrag *gesture,
|
||||
double x, y;
|
||||
double dx, dy;
|
||||
graphene_point_t *c, *p, *d;
|
||||
double l1, l2;
|
||||
|
||||
if (self->dragged == -1)
|
||||
return;
|
||||
@@ -89,53 +172,174 @@ drag_update (GtkGestureDrag *gesture,
|
||||
|
||||
d = &self->points[self->dragged];
|
||||
|
||||
/* before moving the point, record the distances to its neighbors, since
|
||||
* we may want to preserve those
|
||||
*/
|
||||
c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
|
||||
l1 = dist (d, c);
|
||||
c = &self->points[(self->dragged + 1) % self->n_points];
|
||||
l2 = dist (d, c);
|
||||
|
||||
dx = x - d->x;
|
||||
dy = y - d->y;
|
||||
|
||||
d->x += dx;
|
||||
d->y += dy;
|
||||
|
||||
if (self->dragged % 3 == 0)
|
||||
{
|
||||
/* point is on curve */
|
||||
/* dragged point is on curve */
|
||||
|
||||
self->points[(self->dragged - 1 + self->n_points) % self->n_points].x += dx;
|
||||
self->points[(self->dragged - 1 + self->n_points) % self->n_points].y += dy;
|
||||
Operation op, op1, op2;
|
||||
|
||||
self->points[(self->dragged + 1) % self->n_points].x += dx;
|
||||
self->points[(self->dragged + 1) % self->n_points].y += dy;
|
||||
/* first move the point itself */
|
||||
d->x = x;
|
||||
d->y = y;
|
||||
|
||||
/* adjust control points as needed */
|
||||
op = self->point_data[self->dragged / 3].op;
|
||||
op1 = self->point_data[((self->dragged - 1 + self->n_points) % self->n_points) / 3].op;
|
||||
|
||||
if (op1 == LINE)
|
||||
{
|
||||
/* the other endpoint of the line */
|
||||
p = &self->points[(self->dragged - 3 + self->n_points) % self->n_points];
|
||||
|
||||
if (op == CURVE && self->point_data[self->dragged / 3].smooth)
|
||||
{
|
||||
/* adjust the control point after the line segment */
|
||||
c = &self->points[(self->dragged + 1) % self->n_points];
|
||||
opposite_point (d, p, l2, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
c = &self->points[(self->dragged + 1) % self->n_points];
|
||||
c->x += dx;
|
||||
c->y += dy;
|
||||
}
|
||||
|
||||
c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
|
||||
c->x += dx;
|
||||
c->y += dy;
|
||||
|
||||
op2 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
|
||||
if (op2 == CURVE && self->point_data[((self->dragged - 3 + self->n_points) % self->n_points) / 3].smooth)
|
||||
{
|
||||
double l;
|
||||
|
||||
/* adjust the control point before the line segment */
|
||||
c = &self->points[((self->dragged - 4 + self->n_points) % self->n_points)];
|
||||
|
||||
l = dist (c, p);
|
||||
opposite_point (p, d, l, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (op == LINE)
|
||||
{
|
||||
/* the other endpoint of the line */
|
||||
p = &self->points[(self->dragged + 3) % self->n_points];
|
||||
|
||||
if (op1 == CURVE && self->point_data[self->dragged / 3].smooth)
|
||||
{
|
||||
/* adjust the control point before the line segment */
|
||||
c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
|
||||
opposite_point (d, p, l1, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
c = &self->points[(self->dragged -1 + self->n_points) % self->n_points];
|
||||
c->x += dx;
|
||||
c->y += dy;
|
||||
}
|
||||
|
||||
c = &self->points[(self->dragged + 1) % self->n_points];
|
||||
c->x += dx;
|
||||
c->y += dy;
|
||||
|
||||
op2 = self->point_data[((self->dragged + 3) % self->n_points) / 3].op;
|
||||
if (op2 == CURVE && self->point_data[((self->dragged + 3) % self->n_points) / 3].smooth)
|
||||
{
|
||||
double l;
|
||||
|
||||
/* adjust the control point after the line segment */
|
||||
c = &self->points[((self->dragged + 4) % self->n_points)];
|
||||
|
||||
l = dist (c, p);
|
||||
opposite_point (p, d, l, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (op1 != LINE && op != LINE)
|
||||
{
|
||||
self->points[(self->dragged - 1 + self->n_points) % self->n_points].x += dx;
|
||||
self->points[(self->dragged - 1 + self->n_points) % self->n_points].y += dy;
|
||||
|
||||
self->points[(self->dragged + 1) % self->n_points].x += dx;
|
||||
self->points[(self->dragged + 1) % self->n_points].y += dy;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* dragged point is a control point */
|
||||
|
||||
int point;
|
||||
graphene_point_t *p1;
|
||||
Operation op, op1;
|
||||
|
||||
if (self->dragged % 3 == 1)
|
||||
{
|
||||
point = (self->dragged - 1 + self->n_points) % self->n_points;
|
||||
c = &self->points[(self->dragged - 2 + self->n_points) % self->n_points];
|
||||
p = &self->points[point];
|
||||
|
||||
op = self->point_data[point / 3].op;
|
||||
op1 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
|
||||
p1 = &self->points[(self->dragged - 4 + self->n_points) % self->n_points];
|
||||
}
|
||||
else if (self->dragged % 3 == 2)
|
||||
{
|
||||
point = (self->dragged + 1) % self->n_points;
|
||||
c = &self->points[(self->dragged + 2) % self->n_points];
|
||||
p = &self->points[point];
|
||||
|
||||
op = self->point_data[self->dragged / 3].op;
|
||||
op1 = self->point_data[point / 3].op;
|
||||
p1 = &self->points[(self->dragged + 4) % self->n_points];
|
||||
}
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
|
||||
if (self->point_data[point / 3].smooth)
|
||||
if (op == CURVE && self->point_data[point / 3].smooth)
|
||||
{
|
||||
double a, l;
|
||||
if (op1 == CURVE)
|
||||
{
|
||||
double l;
|
||||
|
||||
a = atan2 (self->points[self->dragged].y - p->y, self->points[self->dragged].x - p->x) + M_PI;
|
||||
/* first move the point itself */
|
||||
d->x = x;
|
||||
d->y = y;
|
||||
|
||||
if (self->symmetric)
|
||||
l = dist (d, p);
|
||||
/* then adjust the other control point */
|
||||
if (self->symmetric)
|
||||
l = dist (d, p);
|
||||
else
|
||||
l = dist (c, p);
|
||||
|
||||
opposite_point (p, d, l, c);
|
||||
}
|
||||
else if (op1 == LINE)
|
||||
{
|
||||
graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
|
||||
closest_point (&m, p, p1, d);
|
||||
}
|
||||
else
|
||||
l = dist (c, p);
|
||||
c->x = p->x + l * cos (a);
|
||||
c->y = p->y + l * sin (a);
|
||||
{
|
||||
d->x = x;
|
||||
d->y = y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
d->x = x;
|
||||
d->y = y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,29 +357,85 @@ drag_end (GtkGestureDrag *gesture,
|
||||
self->symmetric = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
maintain_smoothness (DemoWidget *self,
|
||||
int point)
|
||||
{
|
||||
gboolean smooth;
|
||||
Operation op, op1;
|
||||
|
||||
smooth = self->point_data[point / 3].smooth;
|
||||
|
||||
op = self->point_data[point / 3].op;
|
||||
op1 = self->point_data[((point - 1 + self->n_points) % self->n_points) / 3].op;
|
||||
|
||||
if (smooth)
|
||||
{
|
||||
graphene_point_t *p;
|
||||
|
||||
p = &self->points[point];
|
||||
|
||||
if (op == CURVE && op1 == CURVE)
|
||||
{
|
||||
graphene_point_t *c, *c2;
|
||||
float d;
|
||||
|
||||
c = &self->points[(point - 1 + self->n_points) % self->n_points];
|
||||
c2 = &self->points[(point + 1) % self->n_points];
|
||||
|
||||
d = dist (c, p);
|
||||
opposite_point (p, c2, d, c);
|
||||
}
|
||||
else if (op == CURVE && op1 == LINE)
|
||||
{
|
||||
graphene_point_t *c, *p2;
|
||||
float d;
|
||||
|
||||
c = &self->points[(point + 1) % self->n_points];
|
||||
p2 = &self->points[(point - 3 + self->n_points) % self->n_points];
|
||||
|
||||
d = dist (c, p);
|
||||
opposite_point (p, p2, d, c);
|
||||
}
|
||||
else if (op == LINE && op1 == CURVE)
|
||||
{
|
||||
graphene_point_t *c, *p2;
|
||||
float d;
|
||||
|
||||
c = &self->points[(point - 1 + self->n_points) % self->n_points];
|
||||
p2 = &self->points[(point + 3) % self->n_points];
|
||||
|
||||
d = dist (c, p);
|
||||
opposite_point (p, p2, d, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_smooth (GSimpleAction *action,
|
||||
GVariant *value,
|
||||
gpointer data)
|
||||
{
|
||||
DemoWidget *self = DEMO_WIDGET (data);
|
||||
gboolean smooth = g_variant_get_boolean (value);
|
||||
|
||||
self->point_data[self->context / 3].smooth = smooth;
|
||||
if (smooth)
|
||||
{
|
||||
graphene_point_t *p, *c, *c2;
|
||||
float a, d;
|
||||
self->point_data[self->context / 3].smooth = g_variant_get_boolean (value);
|
||||
|
||||
p = &self->points[self->context];
|
||||
c = &self->points[(self->context - 1 + self->n_points) % self->n_points];
|
||||
c2 = &self->points[(self->context + 1 + self->n_points) % self->n_points];
|
||||
maintain_smoothness (self, self->context);
|
||||
|
||||
a = atan2 (c2->y - p->y, c2->x - p->x) + M_PI;
|
||||
d = dist (c, p);
|
||||
c->x = p->x + d * cos (a);
|
||||
c->y = p->y + d * sin (a);
|
||||
}
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
set_operation (GSimpleAction *action,
|
||||
GVariant *value,
|
||||
gpointer data)
|
||||
{
|
||||
DemoWidget *self = DEMO_WIDGET (data);
|
||||
|
||||
self->point_data[self->context / 3].op = op_from_string (g_variant_get_string (value, NULL));
|
||||
|
||||
maintain_smoothness (self, self->context);
|
||||
maintain_smoothness (self, (self->context + 3) % self->n_points);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
@@ -211,6 +471,10 @@ pressed (GtkGestureClick *gesture,
|
||||
action = g_action_map_lookup_action (self->actions, "smooth");
|
||||
g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->point_data[i / 3].smooth));
|
||||
|
||||
action = g_action_map_lookup_action (self->actions, "operation");
|
||||
|
||||
g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (op_to_string (self->point_data[i / 3].op)));
|
||||
|
||||
gtk_popover_set_pointing_to (GTK_POPOVER (self->menu),
|
||||
&(const GdkRectangle){ x, y, 1, 1 });
|
||||
gtk_popover_popup (GTK_POPOVER (self->menu));
|
||||
@@ -250,16 +514,14 @@ released (GtkGestureClick *gesture,
|
||||
if (self->point_data[i / 3].smooth)
|
||||
{
|
||||
graphene_point_t *p, *c, *c2;
|
||||
float a, d;
|
||||
float d;
|
||||
|
||||
p = &self->points[i];
|
||||
c = &self->points[(i - 1 + self->n_points) % self->n_points];
|
||||
c2 = &self->points[(i + 1 + self->n_points) % self->n_points];
|
||||
|
||||
a = atan2 (c2->y - p->y, c2->x - p->x) + M_PI;
|
||||
d = dist (c, p);
|
||||
c->x = p->x + d * cos (a);
|
||||
c->y = p->y + d * sin (a);
|
||||
opposite_point (p, c2, d, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +570,7 @@ init_points (DemoWidget *self)
|
||||
{
|
||||
self->point_data[i].edit = FALSE;
|
||||
self->point_data[i].smooth = TRUE;
|
||||
self->point_data[i].op = CURVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +579,7 @@ demo_widget_init (DemoWidget *self)
|
||||
{
|
||||
GtkGesture *gesture;
|
||||
GMenu *menu;
|
||||
GMenu *section;
|
||||
GMenuItem *item;
|
||||
GSimpleAction *action;
|
||||
|
||||
@@ -338,16 +602,41 @@ demo_widget_init (DemoWidget *self)
|
||||
init_points (self);
|
||||
|
||||
self->actions = G_ACTION_MAP (g_simple_action_group_new ());
|
||||
|
||||
action = g_simple_action_new_stateful ("smooth", NULL, g_variant_new_boolean (FALSE));
|
||||
g_signal_connect (action, "change-state", G_CALLBACK (toggle_smooth), self);
|
||||
g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
|
||||
gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
|
||||
|
||||
action = g_simple_action_new_stateful ("operation", G_VARIANT_TYPE_STRING, g_variant_new_string ("curve"));
|
||||
g_signal_connect (action, "change-state", G_CALLBACK (set_operation), self);
|
||||
g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
|
||||
|
||||
gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
|
||||
|
||||
menu = g_menu_new ();
|
||||
|
||||
item = g_menu_item_new ("Smooth", "point.smooth");
|
||||
g_menu_append_item (menu, item);
|
||||
g_object_unref (item);
|
||||
|
||||
section = g_menu_new ();
|
||||
|
||||
item = g_menu_item_new ("Move", "point.operation::move");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
|
||||
item = g_menu_item_new ("Line", "point.operation::line");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
|
||||
item = g_menu_item_new ("Curve", "point.operation::curve");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
|
||||
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
|
||||
g_object_unref (section);
|
||||
|
||||
self->menu = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
|
||||
g_object_unref (menu);
|
||||
|
||||
@@ -373,19 +662,27 @@ demo_widget_snapshot (GtkWidget *widget,
|
||||
|
||||
if (self->edit)
|
||||
{
|
||||
/* Add the skeleton */
|
||||
|
||||
gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
|
||||
for (i = 1; i < self->n_points; i++)
|
||||
{
|
||||
gboolean edit;
|
||||
gboolean line;
|
||||
|
||||
if (i % 3 == 2)
|
||||
edit = self->point_data[((i + 3) % self->n_points) / 3].edit;
|
||||
else
|
||||
edit = self->point_data[i / 3].edit;
|
||||
|
||||
if (i % 3 == 0)
|
||||
line = self->point_data[((i - 1 + self->n_points) % self->n_points) / 3].op != CURVE;
|
||||
else
|
||||
line = self->point_data[i / 3].op != CURVE;
|
||||
|
||||
if (edit)
|
||||
{
|
||||
if (i % 3 == 2)
|
||||
if (i % 3 == 2 || line)
|
||||
gsk_path_builder_move_to (builder, self->points[i].x, self->points[i].y);
|
||||
else
|
||||
gsk_path_builder_line_to (builder, self->points[i].x, self->points[i].y);
|
||||
@@ -395,15 +692,36 @@ demo_widget_snapshot (GtkWidget *widget,
|
||||
gsk_path_builder_line_to (builder, self->points[0].x, self->points[0].y);
|
||||
}
|
||||
|
||||
/* Add the curve itself */
|
||||
|
||||
gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
|
||||
for (i = 1; i < self->n_points; i += 3)
|
||||
{
|
||||
gsk_path_builder_curve_to (builder,
|
||||
self->points[i].x, self->points[i].y,
|
||||
self->points[(i + 1) % self->n_points].x, self->points[(i + 1) % self->n_points].y,
|
||||
self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % self->n_points].y);
|
||||
switch (self->point_data[i / 3].op)
|
||||
{
|
||||
case MOVE:
|
||||
gsk_path_builder_move_to (builder,
|
||||
self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % self->n_points].y);
|
||||
break;
|
||||
|
||||
case LINE:
|
||||
gsk_path_builder_line_to (builder,
|
||||
self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % self->n_points].y);
|
||||
break;
|
||||
|
||||
case CURVE:
|
||||
gsk_path_builder_curve_to (builder,
|
||||
self->points[i].x, self->points[i].y,
|
||||
self->points[(i + 1) % self->n_points].x, self->points[(i + 1) % self->n_points].y,
|
||||
self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % self->n_points].y);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
/* Stroke everything we have so far */
|
||||
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
stroke = gsk_stroke_new (1);
|
||||
gtk_snapshot_push_stroke (snapshot, path, stroke);
|
||||
@@ -418,6 +736,8 @@ demo_widget_snapshot (GtkWidget *widget,
|
||||
|
||||
if (self->edit)
|
||||
{
|
||||
/* Draw the circles, in several passes, one for each color */
|
||||
|
||||
const char *colors[] = {
|
||||
"red",
|
||||
"green",
|
||||
@@ -434,30 +754,33 @@ demo_widget_snapshot (GtkWidget *widget,
|
||||
switch (j)
|
||||
{
|
||||
case 0:
|
||||
if (i % 3 == 0 &&
|
||||
self->edit &&
|
||||
self->point_data[i / 3].smooth)
|
||||
break;
|
||||
else
|
||||
if (!(i % 3 == 0 &&
|
||||
self->point_data[i / 3].smooth))
|
||||
continue;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (i % 3 == 0 &&
|
||||
self->edit &&
|
||||
!self->point_data[i / 3].smooth)
|
||||
break;
|
||||
else
|
||||
if (!(i % 3 == 0 &&
|
||||
!self->point_data[i / 3].smooth))
|
||||
continue;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (self->edit &&
|
||||
((i % 3 == 1 &&
|
||||
self->point_data[i / 3].edit) ||
|
||||
(i % 3 == 2 &&
|
||||
self->point_data[((i + 3) % self->n_points) / 3].edit)))
|
||||
break;
|
||||
if (i % 3 == 1)
|
||||
{
|
||||
if (!(self->point_data[i / 3].edit &&
|
||||
self->point_data[i / 3].op == CURVE))
|
||||
continue;
|
||||
}
|
||||
else if (i % 3 == 2)
|
||||
{
|
||||
if (!(self->point_data[((i + 3) % self->n_points) / 3].edit &&
|
||||
self->point_data[i / 3].op == CURVE))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
|
||||
Reference in New Issue
Block a user