eel-canvas.c 108 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
 * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
 * All rights reserved.
 *
 * This file is part of the Gnome Library.
 *
 * The Gnome Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Gnome 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
19
 * see <http://www.gnu.org/licenses/>.
20
21
 */
/*
22
 *  @NOTATION@
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 */
/*
 * EelCanvas widget - Tk-like canvas widget for Gnome
 *
 * EelCanvas is basically a port of the Tk toolkit's most excellent canvas widget.  Tk is
 * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties.
 *
 *
 * Authors: Federico Mena <federico@nuclecu.unam.mx>
 *          Raph Levien <raph@gimp.org>
 */

/*
 * TO-DO list for the canvas:
 *
 * - Allow to specify whether EelCanvasImage sizes are in units or pixels (scale or don't scale).
 *
 * - GC put functions for items.
 *
 * - Widget item (finish it).
 *
 * - GList *eel_canvas_gimme_all_items_contained_in_this_area (EelCanvas *canvas, Rectangle area);
 *
 * - Retrofit all the primitive items with microtile support.
 *
 * - Curve support for line item.
 *
 * - Arc item (Havoc has it; to be integrated in EelCanvasEllipse).
 *
 * - Sane font handling API.
 *
 * - Get_arg methods for items:
 *   - How to fetch the outline width and know whether it is in pixels or units?
 */

#include <config.h>

#include <math.h>
#include <string.h>
#include <stdio.h>
#include <gtk/gtk.h>
64
#include <glib/gi18n-lib.h>
Colin Walters's avatar
Colin Walters committed
65
#include <cairo-gobject.h>
66
67
#include "eel-canvas.h"

68
69
70
71
72
static void eel_canvas_request_update (EelCanvas *canvas);
static void group_add (EelCanvasGroup *group,
                       EelCanvasItem  *item);
static void group_remove (EelCanvasGroup *group,
                          EelCanvasItem  *item);
73
74
75
76
77
78
79
static void redraw_and_repick_if_mapped (EelCanvasItem *item);

/*** EelCanvasItem ***/

/* Some convenience stuff */
#define GCI_UPDATE_MASK (EEL_CANVAS_UPDATE_REQUESTED | EEL_CANVAS_UPDATE_DEEP)

80
81
82
83
enum
{
    ITEM_PROP_0,
    ITEM_PROP_VISIBLE
84
85
};

86
87
88
89
90
enum
{
    ITEM_DESTROY,
    ITEM_EVENT,
    ITEM_LAST_SIGNAL
91
92
};

93
94
95
96
static void eel_canvas_item_class_init (EelCanvasItemClass *klass);
static void eel_canvas_item_init (EelCanvasItem *item);
static int  emit_event (EelCanvas *canvas,
                        GdkEvent  *event);
97
98
99

static guint item_signals[ITEM_LAST_SIGNAL];

Cosimo Cecchi's avatar
Cosimo Cecchi committed
100
static GObjectClass *item_parent_class;
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

static gpointer accessible_item_parent_class;
static gpointer accessible_parent_class;


/**
 * eel_canvas_item_get_type:
 *
 * Registers the &EelCanvasItem class if necessary, and returns the type ID
 * associated to it.
 *
 * Return value:  The type ID of the &EelCanvasItem class.
 **/
GType
eel_canvas_item_get_type (void)
{
117
    static GType canvas_item_type = 0;
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    if (!canvas_item_type)
    {
        static const GTypeInfo canvas_item_info =
        {
            sizeof (EelCanvasItemClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) eel_canvas_item_class_init,
            NULL,                       /* class_finalize */
            NULL,                       /* class_data */
            sizeof (EelCanvasItem),
            0,                          /* n_preallocs */
            (GInstanceInitFunc) eel_canvas_item_init
        };

        canvas_item_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED,
                                                   "EelCanvasItem",
                                                   &canvas_item_info,
                                                   0);
    }

    return canvas_item_type;
141
142
143
144
145
146
}

/* Object initialization function for EelCanvasItem */
static void
eel_canvas_item_init (EelCanvasItem *item)
{
147
    item->flags |= EEL_CANVAS_ITEM_VISIBLE;
148
149
}

Ernestas Kulik's avatar
Ernestas Kulik committed
150
151
152
153
154
155
156
157
158
159
160
/* Performs post-creation operations on a canvas item (adding it to its parent
 * group, etc.)
 */
static void
item_post_create_setup (EelCanvasItem *item)
{
    group_add (EEL_CANVAS_GROUP (item->parent), item);

    redraw_and_repick_if_mapped (item);
}

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/**
 * eel_canvas_item_new:
 * @parent: The parent group for the new item.
 * @type: The object type of the item.
 * @first_arg_name: A list of object argument name/value pairs, NULL-terminated,
 * used to configure the item.  For example, "fill_color", "black",
 * "width_units", 5.0, NULL.
 * @Varargs:
 *
 * Creates a new canvas item with @parent as its parent group.  The item is
 * created at the top of its parent's stack, and starts up as visible.  The item
 * is of the specified @type, for example, it can be
 * eel_canvas_rect_get_type().  The list of object arguments/value pairs is
 * used to configure the item.
 *
 * Return value: The newly-created item.
 **/
EelCanvasItem *
179
180
181
182
eel_canvas_item_new (EelCanvasGroup *parent,
                     GType           type,
                     const gchar    *first_arg_name,
                     ...)
183
{
184
185
    EelCanvasItem *item;
    va_list args;
186

187
188
    g_return_val_if_fail (EEL_IS_CANVAS_GROUP (parent), NULL);
    g_return_val_if_fail (g_type_is_a (type, eel_canvas_item_get_type ()), NULL);
189

190
    item = EEL_CANVAS_ITEM (g_object_new (type, NULL));
191

Ernestas Kulik's avatar
Ernestas Kulik committed
192
193
194
    item->parent = EEL_CANVAS_ITEM (parent);
    item->canvas = item->parent->canvas;

195
    va_start (args, first_arg_name);
Ernestas Kulik's avatar
Ernestas Kulik committed
196
    g_object_set_valist (G_OBJECT (item), first_arg_name, args);
197
    va_end (args);
198

Ernestas Kulik's avatar
Ernestas Kulik committed
199
    item_post_create_setup (item);
200

Ernestas Kulik's avatar
Ernestas Kulik committed
201
    return item;
202
203
204
205
}

/* Set_property handler for canvas items */
static void
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
eel_canvas_item_set_property (GObject      *gobject,
                              guint         param_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
    EelCanvasItem *item;

    g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject));

    item = EEL_CANVAS_ITEM (gobject);

    switch (param_id)
    {
        case ITEM_PROP_VISIBLE:
        {
            if (g_value_get_boolean (value))
            {
                eel_canvas_item_show (item);
            }
            else
            {
                eel_canvas_item_hide (item);
            }
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
        }
        break;
    }
238
239
240
241
}

/* Get_property handler for canvas items */
static void
242
243
244
245
eel_canvas_item_get_property (GObject    *gobject,
                              guint       param_id,
                              GValue     *value,
                              GParamSpec *pspec)
246
{
247
    EelCanvasItem *item;
248

249
    g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject));
250

251
    item = EEL_CANVAS_ITEM (gobject);
252

253
254
255
256
257
258
259
260
261
262
263
264
265
266
    switch (param_id)
    {
        case ITEM_PROP_VISIBLE:
        {
            g_value_set_boolean (value, item->flags & EEL_CANVAS_ITEM_VISIBLE);
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
        }
        break;
    }
267
268
269
270
271
}

static void
redraw_and_repick_if_mapped (EelCanvasItem *item)
{
272
273
274
275
276
    if (item->flags & EEL_CANVAS_ITEM_MAPPED)
    {
        eel_canvas_item_request_redraw (item);
        item->canvas->need_repick = TRUE;
    }
277
278
}

Alexander Larsson's avatar
Alexander Larsson committed
279
/* Dispose handler for canvas items */
280
281
282
static void
eel_canvas_item_dispose (GObject *object)
{
283
    EelCanvasItem *item;
284

285
    g_return_if_fail (EEL_IS_CANVAS_ITEM (object));
286

287
    item = EEL_CANVAS_ITEM (object);
288

289
290
291
    if (item->canvas)
    {
        eel_canvas_item_request_redraw (item);
292

293
        /* Make the canvas forget about us */
294

295
296
297
298
299
        if (item == item->canvas->current_item)
        {
            item->canvas->current_item = NULL;
            item->canvas->need_repick = TRUE;
        }
300

301
302
303
304
305
        if (item == item->canvas->new_current_item)
        {
            item->canvas->new_current_item = NULL;
            item->canvas->need_repick = TRUE;
        }
306

307
        eel_canvas_item_ungrab (item);
308

309
310
311
312
        if (item == item->canvas->focused_item)
        {
            item->canvas->focused_item = NULL;
        }
313

314
        /* Normal destroy stuff */
315

316
317
318
319
        if (item->flags & EEL_CANVAS_ITEM_MAPPED)
        {
            (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
        }
320

321
322
323
324
        if (item->flags & EEL_CANVAS_ITEM_REALIZED)
        {
            (*EEL_CANVAS_ITEM_GET_CLASS (item)->unrealize)(item);
        }
325

326
327
328
329
        if (item->parent)
        {
            group_remove (EEL_CANVAS_GROUP (item->parent), item);
        }
Alexander Larsson's avatar
Alexander Larsson committed
330

331
332
        item->canvas = NULL;
    }
333

334
335
    g_object_set_data (object, "in-destruction", GINT_TO_POINTER (1));
    g_signal_emit (object, item_signals[ITEM_DESTROY], 0);
Cosimo Cecchi's avatar
Cosimo Cecchi committed
336

337
    g_object_set_data (object, "in-destruction", NULL);
Cosimo Cecchi's avatar
Cosimo Cecchi committed
338

339
    G_OBJECT_CLASS (item_parent_class)->dispose (object);
340
341
}

Cosimo Cecchi's avatar
Cosimo Cecchi committed
342
343
344
void
eel_canvas_item_destroy (EelCanvasItem *item)
{
345
346
347
348
    if (g_object_get_data (G_OBJECT (item), "in-destruction") == NULL)
    {
        g_object_run_dispose (G_OBJECT (item));
    }
Cosimo Cecchi's avatar
Cosimo Cecchi committed
349
}
Alexander Larsson's avatar
Alexander Larsson committed
350

351
352
353
354
/* Realize handler for canvas items */
static void
eel_canvas_item_realize (EelCanvasItem *item)
{
355
356
357
358
359
360
361
362
363
    if (item->parent && !(item->parent->flags & EEL_CANVAS_ITEM_REALIZED))
    {
        (*EEL_CANVAS_ITEM_GET_CLASS (item->parent)->realize)(item->parent);
    }

    if (item->parent == NULL && !gtk_widget_get_realized (GTK_WIDGET (item->canvas)))
    {
        gtk_widget_realize (GTK_WIDGET (item->canvas));
    }
Bastien Nocera's avatar
Bastien Nocera committed
364

365
    item->flags |= EEL_CANVAS_ITEM_REALIZED;
366

367
    eel_canvas_item_request_update (item);
368
369
370
371
372
373
}

/* Unrealize handler for canvas items */
static void
eel_canvas_item_unrealize (EelCanvasItem *item)
{
374
375
376
377
    if (item->flags & EEL_CANVAS_ITEM_MAPPED)
    {
        (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
    }
Bastien Nocera's avatar
Bastien Nocera committed
378

379
    item->flags &= ~(EEL_CANVAS_ITEM_REALIZED);
380
381
382
383
384
385
}

/* Map handler for canvas items */
static void
eel_canvas_item_map (EelCanvasItem *item)
{
386
    item->flags |= EEL_CANVAS_ITEM_MAPPED;
387
388
389
390
391
392
}

/* Unmap handler for canvas items */
static void
eel_canvas_item_unmap (EelCanvasItem *item)
{
393
    item->flags &= ~(EEL_CANVAS_ITEM_MAPPED);
394
395
396
397
}

/* Update handler for canvas items */
static void
398
399
400
401
eel_canvas_item_update (EelCanvasItem *item,
                        double         i2w_dx,
                        double         i2w_dy,
                        int            flags)
402
{
403
404
    item->flags &= ~(EEL_CANVAS_ITEM_NEED_UPDATE);
    item->flags &= ~(EEL_CANVAS_ITEM_NEED_DEEP_UPDATE);
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
}

/*
 * This routine invokes the update method of the item
 * Please notice, that we take parent to canvas pixel matrix as argument
 * unlike virtual method ::update, whose argument is item 2 canvas pixel
 * matrix
 *
 * I will try to force somewhat meaningful naming for affines (Lauris)
 * General naming rule is FROM2TO, where FROM and TO are abbreviations
 * So p2cpx is Parent2CanvasPixel and i2cpx is Item2CanvasPixel
 * I hope that this helps to keep track of what really happens
 *
 */

static void
eel_canvas_item_invoke_update (EelCanvasItem *item,
422
423
424
                               double         i2w_dx,
                               double         i2w_dy,
                               int            flags)
425
{
426
427
428
    int child_flags;

    child_flags = flags;
429

430
431
    /* apply object flags to child flags */
    child_flags &= ~EEL_CANVAS_UPDATE_REQUESTED;
432

433
434
435
436
    if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE)
    {
        child_flags |= EEL_CANVAS_UPDATE_REQUESTED;
    }
437

438
439
440
441
    if (item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)
    {
        child_flags |= EEL_CANVAS_UPDATE_DEEP;
    }
442

443
444
445
446
447
448
449
    if (child_flags & GCI_UPDATE_MASK)
    {
        if (EEL_CANVAS_ITEM_GET_CLASS (item)->update)
        {
            EEL_CANVAS_ITEM_GET_CLASS (item)->update (item, i2w_dx, i2w_dy, child_flags);
        }
    }
450

451
452
453
    /* If this fail you probably forgot to chain up to
     * EelCanvasItem::update from a derived class */
    g_return_if_fail (!(item->flags & EEL_CANVAS_ITEM_NEED_UPDATE));
454
455
456
457
458
459
460
461
}

/*
 * This routine invokes the point method of the item.
 * The arguments x, y should be in the parent item local coordinates.
 */

static double
462
463
464
465
466
467
eel_canvas_item_invoke_point (EelCanvasItem  *item,
                              double          x,
                              double          y,
                              int             cx,
                              int             cy,
                              EelCanvasItem **actual_item)
468
{
469
    /* Calculate x & y in item local coordinates */
470

471
472
473
474
    if (EEL_CANVAS_ITEM_GET_CLASS (item)->point)
    {
        return EEL_CANVAS_ITEM_GET_CLASS (item)->point (item, x, y, cx, cy, actual_item);
    }
475

476
    return 1e18;
477
478
479
480
481
482
483
484
485
486
487
488
}

/**
 * eel_canvas_item_set:
 * @item: A canvas item.
 * @first_arg_name: The list of object argument name/value pairs used to configure the item.
 * @Varargs:
 *
 * Configures a canvas item.  The arguments in the item are set to the specified
 * values, and the item is repainted as appropriate.
 **/
void
489
490
491
eel_canvas_item_set (EelCanvasItem *item,
                     const gchar   *first_arg_name,
                     ...)
492
{
493
    va_list args;
494

495
496
    va_start (args, first_arg_name);
    g_object_set_valist (G_OBJECT (item), first_arg_name, args);
Ernestas Kulik's avatar
Ernestas Kulik committed
497
    va_end (args);
498

499
    item->canvas->need_repick = TRUE;
500
501
502
503
504
505
506
507
508
509
510
511
512
513
}

/**
 * eel_canvas_item_move:
 * @item: A canvas item.
 * @dx: Horizontal offset.
 * @dy: Vertical offset.
 *
 * Moves a canvas item by creating an affine transformation matrix for
 * translation by using the specified values. This happens in item
 * local coordinate system, so if you have nontrivial transform, it
 * most probably does not do, what you want.
 **/
void
514
515
516
eel_canvas_item_move (EelCanvasItem *item,
                      double         dx,
                      double         dy)
517
{
518
519
    g_return_if_fail (item != NULL);
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
520

521
522
523
524
525
526
    if (!EEL_CANVAS_ITEM_GET_CLASS (item)->translate)
    {
        g_warning ("Item type %s does not implement translate method.\n",
                   g_type_name (G_OBJECT_TYPE (item)));
        return;
    }
527

528
    (*EEL_CANVAS_ITEM_GET_CLASS (item)->translate)(item, dx, dy);
529

530
531
532
533
    if (item->flags & EEL_CANVAS_ITEM_MAPPED)
    {
        item->canvas->need_repick = TRUE;
    }
534

535
536
537
538
539
540
541
542
543
544
545
546
    if (!(item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE))
    {
        item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
        if (item->parent != NULL)
        {
            eel_canvas_item_request_update (item->parent);
        }
        else
        {
            eel_canvas_request_update (item->canvas);
        }
    }
547
548
}

549
550
551
static void
eel_canvas_queue_resize (EelCanvas *canvas)
{
552
553
554
555
    if (gtk_widget_is_drawable (GTK_WIDGET (canvas)))
    {
        gtk_widget_queue_resize (GTK_WIDGET (canvas));
    }
556
557
}

558
559
560
561
/* Convenience function to reorder items in a group's child list.  This puts the
 * specified link after the "before" link. Returns TRUE if the list was changed.
 */
static gboolean
562
563
put_item_after (GList *link,
                GList *before)
564
{
565
    EelCanvasGroup *parent;
566

567
568
569
570
    if (link == before)
    {
        return FALSE;
    }
571

572
    parent = EEL_CANVAS_GROUP (EEL_CANVAS_ITEM (link->data)->parent);
573

574
575
576
577
578
579
    if (before == NULL)
    {
        if (link == parent->item_list)
        {
            return FALSE;
        }
580

581
        link->prev->next = link->next;
582

583
584
585
586
587
588
589
590
        if (link->next)
        {
            link->next->prev = link->prev;
        }
        else
        {
            parent->item_list_end = link->prev;
        }
591

592
593
594
595
596
597
598
599
600
601
602
        link->prev = before;
        link->next = parent->item_list;
        link->next->prev = link;
        parent->item_list = link;
    }
    else
    {
        if ((link == parent->item_list_end) && (before == parent->item_list_end->prev))
        {
            return FALSE;
        }
603

604
605
606
607
        if (link->next)
        {
            link->next->prev = link->prev;
        }
608

609
610
611
612
613
614
615
616
617
        if (link->prev)
        {
            link->prev->next = link->next;
        }
        else
        {
            parent->item_list = link->next;
            parent->item_list->prev = NULL;
        }
618

619
620
        link->prev = before;
        link->next = before->next;
621

622
        link->prev->next = link;
623

624
625
626
627
628
629
630
631
632
633
        if (link->next)
        {
            link->next->prev = link;
        }
        else
        {
            parent->item_list_end = link;
        }
    }
    return TRUE;
634
635
636
637
638
639
640
641
642
643
644
645
646
}


/**
 * eel_canvas_item_raise:
 * @item: A canvas item.
 * @positions: Number of steps to raise the item.
 *
 * Raises the item in its parent's stack by the specified number of positions.
 * If the number of positions is greater than the distance to the top of the
 * stack, then the item is put at the top.
 **/
void
647
648
eel_canvas_item_raise (EelCanvasItem *item,
                       int            positions)
649
{
650
651
    GList *link, *before;
    EelCanvasGroup *parent;
652

653
654
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
    g_return_if_fail (positions >= 0);
655

656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    if (!item->parent || positions == 0)
    {
        return;
    }

    parent = EEL_CANVAS_GROUP (item->parent);
    link = g_list_find (parent->item_list, item);
    g_assert (link != NULL);

    for (before = link; positions && before; positions--)
    {
        before = before->next;
    }

    if (!before)
    {
        before = parent->item_list_end;
    }

    if (put_item_after (link, before))
    {
        redraw_and_repick_if_mapped (item);
    }
679
680
681
682
683
684
685
686
687
688
689
690
691
}


/**
 * eel_canvas_item_lower:
 * @item: A canvas item.
 * @positions: Number of steps to lower the item.
 *
 * Lowers the item in its parent's stack by the specified number of positions.
 * If the number of positions is greater than the distance to the bottom of the
 * stack, then the item is put at the bottom.
 **/
void
692
693
eel_canvas_item_lower (EelCanvasItem *item,
                       int            positions)
694
{
695
696
    GList *link, *before;
    EelCanvasGroup *parent;
697

698
699
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
    g_return_if_fail (positions >= 1);
700

701
    if (!item->parent)
702
703
704
    {
        return;
    }
705

706
707
708
    parent = EEL_CANVAS_GROUP (item->parent);
    link = g_list_find (parent->item_list, item);
    g_assert (link != NULL);
709

710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
    if (link->prev)
    {
        for (before = link->prev; positions && before; positions--)
        {
            before = before->prev;
        }
    }
    else
    {
        before = NULL;
    }

    if (put_item_after (link, before))
    {
        redraw_and_repick_if_mapped (item);
    }
726
727
728
729
730
731
732
733
734
735
736
737
}


/**
 * eel_canvas_item_raise_to_top:
 * @item: A canvas item.
 *
 * Raises an item to the top of its parent's stack.
 **/
void
eel_canvas_item_raise_to_top (EelCanvasItem *item)
{
738
739
    GList *link;
    EelCanvasGroup *parent;
740

741
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
742

743
744
745
746
    if (!item->parent)
    {
        return;
    }
747

748
749
750
    parent = EEL_CANVAS_GROUP (item->parent);
    link = g_list_find (parent->item_list, item);
    g_assert (link != NULL);
751

752
753
754
755
    if (put_item_after (link, parent->item_list_end))
    {
        redraw_and_repick_if_mapped (item);
    }
756
757
758
759
760
761
762
763
764
765
766
767
}


/**
 * eel_canvas_item_lower_to_bottom:
 * @item: A canvas item.
 *
 * Lowers an item to the bottom of its parent's stack.
 **/
void
eel_canvas_item_lower_to_bottom (EelCanvasItem *item)
{
768
769
    GList *link;
    EelCanvasGroup *parent;
770

771
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
772

773
774
775
776
    if (!item->parent)
    {
        return;
    }
777

778
779
780
    parent = EEL_CANVAS_GROUP (item->parent);
    link = g_list_find (parent->item_list, item);
    g_assert (link != NULL);
781

782
783
784
785
    if (put_item_after (link, NULL))
    {
        redraw_and_repick_if_mapped (item);
    }
786
787
788
789
790
791
792
793
794
795
796
797
798
}

/**
 * eel_canvas_item_send_behind:
 * @item: A canvas item.
 * @behind_item: The canvas item to put item behind, or NULL
 *
 * Moves item to a in the position in the stacking order so that
 * it is placed immediately below behind_item, or at the top if
 * behind_item is NULL.
 **/
void
eel_canvas_item_send_behind (EelCanvasItem *item,
799
                             EelCanvasItem *behind_item)
800
{
801
802
    GList *item_list;
    int item_position, behind_position;
803

804
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
805

806
807
808
809
810
    if (behind_item == NULL)
    {
        eel_canvas_item_raise_to_top (item);
        return;
    }
811

812
813
    g_return_if_fail (EEL_IS_CANVAS_ITEM (behind_item));
    g_return_if_fail (item->parent == behind_item->parent);
814

815
    item_list = EEL_CANVAS_GROUP (item->parent)->item_list;
816

817
818
819
820
821
    item_position = g_list_index (item_list, item);
    g_assert (item_position != -1);
    behind_position = g_list_index (item_list, behind_item);
    g_assert (behind_position != -1);
    g_assert (item_position != behind_position);
822

823
824
825
826
827
828
829
830
831
832
833
834
835
    if (item_position == behind_position - 1)
    {
        return;
    }

    if (item_position < behind_position)
    {
        eel_canvas_item_raise (item, (behind_position - 1) - item_position);
    }
    else
    {
        eel_canvas_item_lower (item, item_position - behind_position);
    }
836
837
838
839
840
841
842
843
844
845
846
}

/**
 * eel_canvas_item_show:
 * @item: A canvas item.
 *
 * Shows a canvas item.  If the item was already shown, then no action is taken.
 **/
void
eel_canvas_item_show (EelCanvasItem *item)
{
847
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
848

849
850
851
    if (!(item->flags & EEL_CANVAS_ITEM_VISIBLE))
    {
        item->flags |= EEL_CANVAS_ITEM_VISIBLE;
852

853
854
855
856
        if (!(item->flags & EEL_CANVAS_ITEM_REALIZED))
        {
            (*EEL_CANVAS_ITEM_GET_CLASS (item)->realize)(item);
        }
857

858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
        if (item->parent != NULL)
        {
            if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) &&
                item->parent->flags & EEL_CANVAS_ITEM_MAPPED)
            {
                (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item);
            }
        }
        else
        {
            if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) &&
                gtk_widget_get_mapped (GTK_WIDGET (item->canvas)))
            {
                (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item);
            }
        }

        redraw_and_repick_if_mapped (item);
        eel_canvas_queue_resize (item->canvas);
    }
878
879
880
881
882
883
884
885
886
887
888
889
890
}


/**
 * eel_canvas_item_hide:
 * @item: A canvas item.
 *
 * Hides a canvas item.  If the item was already hidden, then no action is
 * taken.
 **/
void
eel_canvas_item_hide (EelCanvasItem *item)
{
891
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
892

893
894
895
    if (item->flags & EEL_CANVAS_ITEM_VISIBLE)
    {
        item->flags &= ~EEL_CANVAS_ITEM_VISIBLE;
896

897
        redraw_and_repick_if_mapped (item);
898

899
900
901
902
        if (item->flags & EEL_CANVAS_ITEM_MAPPED)
        {
            (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
        }
903

904
        eel_canvas_queue_resize (item->canvas);
905

906
907
        /* No need to unrealize when we just want to hide */
    }
908
909
910
}


911
912
913
914
/*
 * Prepare the window for grabbing, i.e. show it.
 */
static void
915
916
917
seat_grab_prepare_window (GdkSeat   *seat,
                          GdkWindow *window,
                          gpointer   user_data)
918
{
919
    gdk_window_show (window);
920
921
}

922
923
924
925
926
/**
 * eel_canvas_item_grab:
 * @item: A canvas item.
 * @event_mask: Mask of events that will be sent to this item.
 * @cursor: If non-NULL, the cursor that will be used while the grab is active.
927
 * @event: The event, triggering the grab, if any.
928
929
 *
 * Specifies that all events that match the specified event mask should be sent
930
 * to the specified item, and also grabs the seat by calling gdk_seat_grab().
931
932
 * If @cursor is not NULL, then that cursor is used while the grab is active.
 *
933
 * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If
934
 * the specified item was hidden by calling eel_canvas_item_hide(), then it
935
936
 * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling
 * gdk_seat_grab().
937
 **/
938
GdkGrabStatus
939
940
941
942
eel_canvas_item_grab (EelCanvasItem  *item,
                      GdkEventMask    event_mask,
                      GdkCursor      *cursor,
                      const GdkEvent *event)
943
{
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
    GdkGrabStatus retval;
    GdkDisplay *display;
    GdkSeat *seat;

    g_return_val_if_fail (EEL_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
    g_return_val_if_fail (gtk_widget_get_mapped (GTK_WIDGET (item->canvas)),
                          GDK_GRAB_NOT_VIEWABLE);

    if (item->canvas->grabbed_item)
    {
        return GDK_GRAB_ALREADY_GRABBED;
    }

    if (!(item->flags & EEL_CANVAS_ITEM_MAPPED))
    {
        return GDK_GRAB_NOT_VIEWABLE;
    }

    display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
    seat = gdk_display_get_default_seat (display);

    retval = gdk_seat_grab (seat,
                            gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas)),
                            GDK_SEAT_CAPABILITY_ALL_POINTING,
                            FALSE,
                            cursor,
                            event,
                            seat_grab_prepare_window,
                            NULL);

    if (retval != GDK_GRAB_SUCCESS)
    {
        return retval;
    }
978

979
980
981
    item->canvas->grabbed_item = item;
    item->canvas->grabbed_event_mask = event_mask;
    item->canvas->current_item = item;     /* So that events go to the grabbed item */
982

983
    return retval;
984
985
986
987
988
989
990
991
}


/**
 * eel_canvas_item_ungrab:
 * @item: A canvas item that holds a grab.
 *
 * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
992
 * seat.
993
994
 **/
void
995
eel_canvas_item_ungrab (EelCanvasItem *item)
996
{
997
998
    GdkDisplay *display;
    GdkSeat *seat;
999

1000
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
1001

1002
1003
1004
1005
    if (item->canvas->grabbed_item != item)
    {
        return;
    }
1006

1007
1008
    display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
    seat = gdk_display_get_default_seat (display);
1009

1010
1011
    item->canvas->grabbed_item = NULL;
    gdk_seat_ungrab (seat);
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
}

/**
 * eel_canvas_item_i2w:
 * @item: A canvas item.
 * @x: X coordinate to convert (input/output value).
 * @y: Y coordinate to convert (input/output value).
 *
 * Converts a coordinate pair from item-relative coordinates to world
 * coordinates.
 **/
void
1024
1025
1026
eel_canvas_item_i2w (EelCanvasItem *item,
                     double        *x,
                     double        *y)
1027
{
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
    g_return_if_fail (x != NULL);
    g_return_if_fail (y != NULL);

    item = item->parent;
    while (item)
    {
        if (EEL_IS_CANVAS_GROUP (item))
        {
            *x += EEL_CANVAS_GROUP (item)->xpos;
            *y += EEL_CANVAS_GROUP (item)->ypos;
        }
1040

1041
1042
        item = item->parent;
    }
1043
1044
1045
1046
}

/* Returns whether the item is an inferior of or is equal to the parent. */
static int
1047
1048
is_descendant (EelCanvasItem *item,
               EelCanvasItem *parent)
1049
{
1050
1051
1052
1053
1054
1055
1056
    for (; item; item = item->parent)
    {
        if (item == parent)
        {
            return TRUE;
        }
    }
1057

1058
    return FALSE;
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
}

/**
 * eel_canvas_item_grab_focus:
 * @item: A canvas item.
 *
 * Makes the specified item take the keyboard focus, so all keyboard events will
 * be sent to it.  If the canvas widget itself did not have the focus, it grabs
 * it as well.
 **/
Ernestas Kulik's avatar
Ernestas Kulik committed
1069
static void
1070
1071
eel_canvas_item_grab_focus (EelCanvasItem *item)
{
1072
1073
    EelCanvasItem *focused_item;
    GdkEvent ev;
1074

1075
1076
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
    g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas)));
1077

1078
    focused_item = item->canvas->focused_item;
1079

1080
1081
1082
1083
1084
1085
    if (focused_item)
    {
        ev.focus_change.type = GDK_FOCUS_CHANGE;
        ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas));
        ev.focus_change.send_event = FALSE;
        ev.focus_change.in = FALSE;
1086

1087
1088
        emit_event (item->canvas, &ev);
    }
1089

1090
1091
    item->canvas->focused_item = item;
    gtk_widget_grab_focus (GTK_WIDGET (item->canvas));
1092

1093
1094
1095
1096
1097
1098
    if (focused_item)
    {
        ev.focus_change.type = GDK_FOCUS_CHANGE;
        ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas));
        ev.focus_change.send_event = FALSE;
        ev.focus_change.in = TRUE;
1099

1100
1101
        emit_event (item->canvas, &ev);
    }
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
}


/**
 * eel_canvas_item_get_bounds:
 * @item: A canvas item.
 * @x1: Leftmost edge of the bounding box (return value).
 * @y1: Upper edge of the bounding box (return value).
 * @x2: Rightmost edge of the bounding box (return value).
 * @y2: Lower edge of the bounding box (return value).
 *
 * Queries the bounding box of a canvas item.  The bounds are returned in the
 * coordinate system of the item's parent.
 **/
void
1117
1118
1119
1120
1121
eel_canvas_item_get_bounds (EelCanvasItem *item,
                            double        *x1,
                            double        *y1,
                            double        *x2,
                            double        *y2)
1122
{
1123
    double tx1, ty1, tx2, ty2;
1124

1125
    g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
1126

1127
    tx1 = ty1 = tx2 = ty2 = 0.0;
1128

1129
    /* Get the item's bounds in its coordinate system */
1130

1131
1132
1133
1134
    if (EEL_CANVAS_ITEM_GET_CLASS (item)->bounds)
    {
        (*EEL_CANVAS_ITEM_GET_CLASS (item)->bounds)(item, &tx1, &ty1, &tx2, &ty2);
    }
1135

1136
    /* Return the values */
1137

1138
1139
1140
1141
    if (x1)
    {
        *x1 = tx1;
    }
1142

1143
1144
1145
1146
    if (y1)
    {
        *y1 = ty1;
    }
1147

1148
1149
1150
1151
    if (x2)
    {
        *x2 = tx2;
    }
1152

1153
1154
1155
1156
    if (y2)
    {
        *y2 = ty2;
    }
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
}


/**
 * eel_canvas_item_request_update
 * @item: A canvas item.
 *
 * To be used only by item implementations.  Requests that the canvas queue an
 * update for the specified item.
 **/
void
eel_canvas_item_request_update (EelCanvasItem *item)
{
1170
1171
1172
1173
    if (NULL == item->canvas)
    {
        return;
    }
1174

1175
    g_return_if_fail (!item->canvas->doing_update);
1176

1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
    if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE)
    {
        return;
    }

    item->flags |= EEL_CANVAS_ITEM_NEED_UPDATE;

    if (item->parent != NULL)
    {
        /* Recurse up the tree */
        eel_canvas_item_request_update (item->parent);
    }
    else
    {
        /* Have reached the top of the tree, make sure the update call gets scheduled. */
        eel_canvas_request_update (item->canvas);
    }
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
}

/**
 * eel_canvas_item_request_update
 * @item: A canvas item.
 *
 * Convenience function that informs a canvas that the specified item needs
 * to be repainted. To be used by item implementations
 **/
void
eel_canvas_item_request_redraw (EelCanvasItem *item)
{
1206
1207
1208
1209
1210
1211
    if (item->flags & EEL_CANVAS_ITEM_MAPPED)
    {
        eel_canvas_request_redraw (item->canvas,
                                   item->x1, item->y1,
                                   item->x2 + 1, item->y2 + 1);
    }
1212
1213
1214
1215
1216
1217
1218
}



/*** EelCanvasGroup ***/


1219
1220
1221
1222
1223
enum
{
    GROUP_PROP_0,
    GROUP_PROP_X,
    GROUP_PROP_Y
1224
1225
1226
};


1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
static void eel_canvas_group_class_init (EelCanvasGroupClass *klass);
static void eel_canvas_group_init (EelCanvasGroup *group);
static void eel_canvas_group_set_property (GObject      *object,
                                           guint         param_id,
                                           const GValue *value,
                                           GParamSpec   *pspec);
static void eel_canvas_group_get_property (GObject    *object,
                                           guint       param_id,
                                           GValue     *value,
                                           GParamSpec *pspec);

static void eel_canvas_group_destroy (EelCanvasItem *object);

static void   eel_canvas_group_update (EelCanvasItem *item,
                                       double         i2w_dx,
                                       double         i2w_dy,
                                       int            flags);
static void   eel_canvas_group_unrealize (EelCanvasItem *item);
static void   eel_canvas_group_map (EelCanvasItem *item);
static void   eel_canvas_group_unmap (EelCanvasItem *item);
static void   eel_canvas_group_draw (EelCanvasItem  *item,
                                     cairo_t        *cr,
                                     cairo_region_t *region);
static double eel_canvas_group_point (EelCanvasItem  *item,
                                      double          x,
                                      double          y,
                                      int             cx,
                                      int             cy,
                                      EelCanvasItem **actual_item);
static void   eel_canvas_group_translate (EelCanvasItem *item,
                                          double         dx,
                                          double         dy);
static void   eel_canvas_group_bounds (EelCanvasItem *item,
                                       double        *x1,
                                       double        *y1,
                                       double        *x2,
                                       double        *y2);
1264
1265
1266
1267


static EelCanvasItemClass *group_parent_class;

Alexander Larsson's avatar
Alexander Larsson committed
1268

1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
/**
 * eel_canvas_group_get_type:
 *
 * Registers the &EelCanvasGroup class if necessary, and returns the type ID
 * associated to it.
 *
 * Return value:  The type ID of the &EelCanvasGroup class.
 **/
GType
eel_canvas_group_get_type (void)
{
1280
    static GType group_type = 0;
1281

1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
    if (!group_type)
    {
        static const GTypeInfo group_info =
        {
            sizeof (EelCanvasGroupClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) eel_canvas_group_class_init,
            NULL,                       /* class_finalize */
            NULL,                       /* class_data */
            sizeof (EelCanvasGroup),
            0,                          /* n_preallocs */
            (GInstanceInitFunc) eel_canvas_group_init
        };

        group_type = g_type_register_static (eel_canvas_item_get_type (),
                                             "EelCanvasGroup",
                                             &group_info,
                                             0);
    }

    return group_type;
1304
1305
1306
1307
}

/* Class initialization function for EelCanvasGroupClass */
static void
Alexander Larsson's avatar
Alexander Larsson committed
1308
eel_canvas_group_class_init (EelCanvasGroupClass *klass)
1309
{
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
    GObjectClass *gobject_class;
    EelCanvasItemClass *item_class;

    gobject_class = (GObjectClass *) klass;
    item_class = (EelCanvasItemClass *) klass;

    group_parent_class = g_type_class_peek_parent (klass);

    gobject_class->set_property = eel_canvas_group_set_property;
    gobject_class->get_property = eel_canvas_group_get_property;

    g_object_class_install_property
        (gobject_class, GROUP_PROP_X,
        g_param_spec_double ("x",
                             _("X"),
                             _("X"),
                             -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                             G_PARAM_READWRITE));
    g_object_class_install_property
        (gobject_class, GROUP_PROP_Y,
        g_param_spec_double ("y",
                             _("Y"),
                             _("Y"),
                             -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                             G_PARAM_READWRITE));

    item_class->destroy = eel_canvas_group_destroy;
    item_class->update = eel_canvas_group_update;
    item_class->unrealize = eel_canvas_group_unrealize;
    item_class->map = eel_canvas_group_map;
    item_class->unmap = eel_canvas_group_unmap;
    item_class->draw = eel_canvas_group_draw;
    item_class->point = eel_canvas_group_point;
    item_class->translate = eel_canvas_group_translate;
    item_class->bounds = eel_canvas_group_bounds;
1345
1346
1347
1348
1349
1350
}

/* Object initialization function for EelCanvasGroup */
static void
eel_canvas_group_init (EelCanvasGroup *group)
{
1351
1352
    group->xpos = 0.0;
    group->ypos = 0.0;
1353
1354
1355
1356
}

/* Set_property handler for canvas groups */
static void
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
eel_canvas_group_set_property (GObject      *gobject,
                               guint         param_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
    EelCanvasItem *item;
    EelCanvasGroup *group;
    double old;
    gboolean moved;

    g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject));

    item = EEL_CANVAS_ITEM (gobject);
    group = EEL_CANVAS_GROUP (gobject);

    moved = FALSE;
    switch (param_id)
    {
        case GROUP_PROP_X:
        {
            old = group->xpos;
            group->xpos = g_value_get_double (value);
            if (old != group->xpos)
            {
                moved = TRUE;
            }
        }
        break;

        case GROUP_PROP_Y:
        {
            old = group->ypos;
            group->ypos = g_value_get_double (value);
            if (old != group->ypos)
            {
                moved = TRUE;
            }
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
        }
        break;
    }

    if (moved)
    {
        item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
        if (item->parent != NULL)
        {
            eel_canvas_item_request_update (item->parent);
        }
        else
        {
            eel_canvas_request_update (item->canvas);
        }
    }
1416
1417
1418
1419
}

/* Get_property handler for canvas groups */
static void
1420
1421
1422
1423
eel_canvas_group_get_property (GObject    *gobject,
                               guint       param_id,
                               GValue     *value,
                               GParamSpec *pspec)
1424
{
1425
    EelCanvasGroup *group;
1426

1427
    g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject));
1428

1429
    group = EEL_CANVAS_GROUP (gobject);
1430

1431
1432
1433
1434
1435
1436
1437
    switch (param_id)
    {
        case GROUP_PROP_X:
        {
            g_value_set_double (value, group->xpos);
        }
        break;
1438

1439
1440
1441
1442
1443
        case GROUP_PROP_Y:
        {
            g_value_set_double (value, group->ypos);
        }
        break;
1444

1445
1446
1447
1448
1449
1450
        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
        }
        break;
    }
1451
1452
1453
1454
}

/* Destroy handler for canvas groups */
static void
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1455
eel_canvas_group_destroy (EelCanvasItem *object)
1456
{
1457
1458
1459
    EelCanvasGroup *group;
    EelCanvasItem *child;
    GList *list;
1460

1461
    g_return_if_fail (EEL_IS_CANVAS_GROUP (object));
1462

1463
    group = EEL_CANVAS_GROUP (object);
1464

1465
1466
1467
1468
1469
    list = group->item_list;
    while (list)
    {
        child = list->data;
        list = list->next;
1470

1471
1472
        eel_canvas_item_destroy (child);
    }
1473

1474
1475
1476
1477