]> Pileus Git - ~andy/gtk/blob - gtk/gtkhandlebox.c
fixed destroy handler, so it doesn't segfault with the new refcounting
[~andy/gtk] / gtk / gtkhandlebox.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 1998 Elliot Lee
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20
21 #include <stdlib.h>
22 #include "gdk/gdkx.h"
23 #include "gtkhandlebox.h"
24 #include "gtkmain.h"
25 #include "gtksignal.h"
26 #include "gtkwindow.h"
27
28
29 #define DRAG_HANDLE_SIZE 10
30 #define BORDER_SIZE 5
31 #define GHOST_HEIGHT 3
32 #define SNAP_TOLERANCE 10
33
34
35 static void gtk_handle_box_class_init     (GtkHandleBoxClass *klass);
36 static void gtk_handle_box_init           (GtkHandleBox      *handle_box);
37 static void gtk_handle_box_destroy        (GtkObject         *object);
38 static void gtk_handle_box_map            (GtkWidget         *widget);
39 static void gtk_handle_box_unmap          (GtkWidget         *widget);
40 static void gtk_handle_box_realize        (GtkWidget         *widget);
41 static void gtk_handle_box_unrealize      (GtkWidget         *widget);
42 static void gtk_handle_box_size_request   (GtkWidget         *widget,
43                                            GtkRequisition    *requisition);
44 static void gtk_handle_box_size_allocate  (GtkWidget         *widget,
45                                            GtkAllocation     *allocation);
46 static void gtk_handle_box_draw_ghost     (GtkWidget         *widget);
47 static void gtk_handle_box_paint          (GtkWidget         *widget,
48                                            GdkEventExpose    *event,
49                                            GdkRectangle      *area);
50 static void gtk_handle_box_draw           (GtkWidget         *widget,
51                                            GdkRectangle      *area);
52 static gint gtk_handle_box_expose         (GtkWidget         *widget,
53                                            GdkEventExpose    *event);
54 static gint gtk_handle_box_button_changed (GtkWidget         *widget,
55                                            GdkEventButton    *event);
56 static gint gtk_handle_box_motion         (GtkWidget         *widget,
57                                            GdkEventMotion    *event);
58 static gint gtk_handle_box_delete_float   (GtkWidget         *widget,
59                                            GdkEvent          *event,
60                                            gpointer           data);
61
62
63 static GtkBinClass *parent_class;
64
65
66 guint
67 gtk_handle_box_get_type (void)
68 {
69   static guint handle_box_type = 0;
70
71   if (!handle_box_type)
72     {
73       GtkTypeInfo handle_box_info =
74       {
75         "GtkHandleBox",
76         sizeof (GtkHandleBox),
77         sizeof (GtkHandleBoxClass),
78         (GtkClassInitFunc) gtk_handle_box_class_init,
79         (GtkObjectInitFunc) gtk_handle_box_init,
80         (GtkArgSetFunc) NULL,
81         (GtkArgGetFunc) NULL,
82       };
83
84       handle_box_type = gtk_type_unique (gtk_bin_get_type (), &handle_box_info);
85     }
86
87   return handle_box_type;
88 }
89
90 static void
91 gtk_handle_box_class_init (GtkHandleBoxClass *class)
92 {
93   GtkWidgetClass *widget_class;
94   GtkObjectClass *object_class;
95
96   object_class = (GtkObjectClass *) class;
97   widget_class = (GtkWidgetClass *) class;
98
99   parent_class = gtk_type_class (gtk_bin_get_type ());
100
101   object_class->destroy = gtk_handle_box_destroy;
102
103   widget_class->map = gtk_handle_box_map;
104   widget_class->unmap = gtk_handle_box_unmap;
105   widget_class->realize = gtk_handle_box_realize;
106   widget_class->unrealize = gtk_handle_box_unrealize;
107   widget_class->size_request = gtk_handle_box_size_request;
108   widget_class->size_allocate = gtk_handle_box_size_allocate;
109   widget_class->draw = gtk_handle_box_draw;
110   widget_class->expose_event = gtk_handle_box_expose;
111   widget_class->button_press_event = gtk_handle_box_button_changed;
112   widget_class->button_release_event = gtk_handle_box_button_changed;
113   widget_class->motion_notify_event = gtk_handle_box_motion;
114 }
115
116 static void
117 gtk_handle_box_init (GtkHandleBox *handle_box)
118 {
119   GTK_WIDGET_UNSET_FLAGS (handle_box, GTK_NO_WINDOW);
120   GTK_WIDGET_SET_FLAGS (handle_box, GTK_BASIC); /* FIXME: are we really a basic widget? */
121
122   handle_box->steady_window = NULL;
123   handle_box->float_window = NULL;
124   handle_box->is_being_dragged = FALSE;
125   handle_box->is_onroot = FALSE;
126   handle_box->fleur_cursor = gdk_cursor_new (GDK_FLEUR);
127   handle_box->dragoff_x = 0;
128   handle_box->dragoff_y = 0;
129   handle_box->steady_x = 0;
130   handle_box->steady_x = 0;
131 }
132
133 GtkWidget*
134 gtk_handle_box_new (void)
135 {
136   return GTK_WIDGET (gtk_type_new (gtk_handle_box_get_type ()));
137 }
138
139 static void
140 gtk_handle_box_destroy (GtkObject *object)
141 {
142   GtkHandleBox *hb;
143
144   g_return_if_fail (object != NULL);
145   g_return_if_fail (GTK_IS_HANDLE_BOX (object));
146
147   hb = GTK_HANDLE_BOX (object);
148
149   gdk_cursor_destroy (hb->fleur_cursor);
150   hb->fleur_cursor = NULL;
151
152   if (GTK_OBJECT_CLASS (parent_class)->destroy)
153     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
154 }
155
156 static void
157 gtk_handle_box_map (GtkWidget *widget)
158 {
159   GtkBin *bin;
160   GtkHandleBox *hb;
161
162   g_return_if_fail (widget != NULL);
163   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
164
165   GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
166
167   bin = GTK_BIN (widget);
168   hb = GTK_HANDLE_BOX (widget);
169
170   gdk_window_show (hb->steady_window);
171   gdk_window_show (widget->window);
172
173   if (bin->child
174       && GTK_WIDGET_VISIBLE (bin->child)
175       && !GTK_WIDGET_MAPPED (bin->child))
176     gtk_widget_map (bin->child);
177 }
178
179 static void
180 gtk_handle_box_unmap (GtkWidget *widget)
181 {
182   GtkHandleBox *hb;
183
184   g_return_if_fail (widget != NULL);
185   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
186
187   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
188
189   hb = GTK_HANDLE_BOX (widget);
190
191   gdk_window_hide (widget->window);
192   gdk_window_hide (hb->steady_window);
193 }
194
195 static void
196 gtk_handle_box_realize (GtkWidget *widget)
197 {
198   GdkWindowAttr attributes;
199   gint attributes_mask;
200   GtkHandleBox *hb;
201
202   g_return_if_fail (widget != NULL);
203   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
204
205   hb = GTK_HANDLE_BOX (widget);
206
207   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
208
209   /* FIXME: we need a property that would tell the window manager not
210    * to put decoration on this window.  This is not part of the ICCCM,
211    * so we'll have to define our own (a la KWM) and hack some window
212    * managers to support it.
213    */
214
215   hb->float_window = gtk_window_new (GTK_WINDOW_DIALOG);
216   gtk_window_set_policy (GTK_WINDOW (hb->float_window), FALSE, FALSE, TRUE);
217   gtk_container_border_width (GTK_CONTAINER (hb->float_window), 0);
218   gtk_signal_connect (GTK_OBJECT (hb->float_window), "delete_event",
219                       (GtkSignalFunc) gtk_handle_box_delete_float,
220                       hb);
221   
222   attributes.x = widget->allocation.x;
223   attributes.y = widget->allocation.y;
224   attributes.width = widget->allocation.width;
225   attributes.height = widget->allocation.height;
226   attributes.window_type = GDK_WINDOW_CHILD;
227   attributes.wclass = GDK_INPUT_OUTPUT;
228   attributes.visual = gtk_widget_get_visual (widget);
229   attributes.colormap = gtk_widget_get_colormap (widget);
230   attributes.event_mask = (gtk_widget_get_events (widget)
231                            | GDK_EXPOSURE_MASK);
232
233   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
234
235   hb->steady_window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
236   gdk_window_set_user_data (hb->steady_window, widget);
237
238   attributes.x = 0;
239   attributes.y = 0;
240   attributes.event_mask |= (GDK_BUTTON1_MOTION_MASK
241                             | GDK_POINTER_MOTION_HINT_MASK
242                             | GDK_BUTTON_PRESS_MASK
243                             | GDK_BUTTON_RELEASE_MASK);
244
245   widget->window = gdk_window_new (hb->steady_window, &attributes, attributes_mask);
246   gdk_window_set_user_data (widget->window, widget);
247
248   widget->style = gtk_style_attach (widget->style, widget->window);
249   gtk_style_set_background (widget->style, hb->steady_window, GTK_STATE_NORMAL);
250   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
251 }
252
253 static void
254 gtk_handle_box_unrealize (GtkWidget *widget)
255 {
256   GtkHandleBox *hb;
257
258   g_return_if_fail (widget != NULL);
259   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
260
261   GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED | GTK_MAPPED);
262
263   gtk_style_detach (widget->style);
264
265   hb = GTK_HANDLE_BOX (widget);
266
267   if (widget->window)
268     {
269       gdk_window_set_user_data (widget->window, NULL);
270       gdk_window_destroy (widget->window);
271       widget->window = NULL;
272     }
273
274   if (hb->steady_window)
275     {
276       gdk_window_set_user_data (hb->steady_window, NULL);
277       gdk_window_destroy (hb->steady_window);
278       hb->steady_window = NULL;
279     }
280
281   gtk_widget_destroy (hb->float_window);
282   hb->float_window = NULL;
283 }
284
285 static void
286 gtk_handle_box_size_request (GtkWidget      *widget,
287                              GtkRequisition *requisition)
288 {
289   GtkBin *bin;
290   GtkHandleBox *hb;
291
292   g_return_if_fail (widget != NULL);
293   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
294   g_return_if_fail (requisition != NULL);
295
296   bin = GTK_BIN (widget);
297   hb = GTK_HANDLE_BOX (widget);
298
299   requisition->width = DRAG_HANDLE_SIZE + GTK_CONTAINER (widget)->border_width * 2;
300   requisition->height = GTK_CONTAINER (widget)->border_width * 2;
301
302   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
303     {
304       gtk_widget_size_request (bin->child, &bin->child->requisition);
305
306       requisition->width += bin->child->requisition.width;
307       requisition->height += bin->child->requisition.height;
308     }
309
310   hb->real_requisition = *requisition;
311   if (hb->is_onroot)
312       requisition->height = GHOST_HEIGHT;
313   /* FIXME: Should also set requisition->width to a small value? */
314 }
315
316 static void
317 gtk_handle_box_size_allocate (GtkWidget     *widget,
318                               GtkAllocation *allocation)
319 {
320   GtkBin *bin;
321   GtkAllocation child_allocation;
322   GtkHandleBox *hb;
323   gint border_width;
324
325   g_return_if_fail (widget != NULL);
326   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
327   g_return_if_fail (allocation != NULL);
328
329   widget->allocation = *allocation;
330   bin = GTK_BIN (widget);
331   hb = GTK_HANDLE_BOX (widget);
332
333   border_width = GTK_CONTAINER (widget)->border_width;
334
335   if (GTK_WIDGET_REALIZED (widget))
336     {
337       if (!hb->is_onroot)
338         {
339           gdk_window_move_resize (hb->steady_window,
340                                   allocation->x + border_width,
341                                   allocation->y + border_width,
342                                   allocation->width - border_width * 2,
343                                   allocation->height - border_width * 2);
344           gdk_window_move_resize (widget->window,
345                                   0,
346                                   0,
347                                   allocation->width - border_width * 2,
348                                   allocation->height - border_width * 2);
349         }
350       else
351         {
352           gtk_widget_set_usize (hb->float_window,
353                                 hb->real_requisition.width,
354                                 hb->real_requisition.height);
355           gdk_window_resize (widget->window,
356                              hb->real_requisition.width,
357                              hb->real_requisition.height);
358           gdk_window_move_resize (hb->steady_window,
359                                   allocation->x + border_width,
360                                   allocation->y + border_width,
361                                   allocation->width - border_width * 2,
362                                   GHOST_HEIGHT);
363         }
364     }
365
366   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
367     {
368       child_allocation.x = DRAG_HANDLE_SIZE;
369       child_allocation.y = 0;
370
371       if (hb->is_onroot)
372         {
373           child_allocation.width = hb->real_requisition.width - DRAG_HANDLE_SIZE;
374           child_allocation.height = hb->real_requisition.height;
375         }
376       else
377         {
378           child_allocation.width = allocation->width - DRAG_HANDLE_SIZE - border_width * 2;
379           child_allocation.height = allocation->height - border_width * 2;
380         }
381
382       gtk_widget_size_allocate (bin->child, &child_allocation);
383     }
384 }
385
386 static void
387 gtk_handle_box_draw_ghost (GtkWidget *widget)
388 {
389   gtk_draw_hline (widget->style,
390                   GTK_HANDLE_BOX (widget)->steady_window,
391                   GTK_WIDGET_STATE (widget),
392                   0,
393                   widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2,
394                   0);
395 }
396
397 static void
398 gtk_handle_box_paint (GtkWidget      *widget,
399                       GdkEventExpose *event,
400                       GdkRectangle   *area)
401 {
402   GtkBin *bin;
403   GtkHandleBox *hb;
404   GdkRectangle child_area;
405   GdkEventExpose child_event;
406   gint x;
407
408   bin = GTK_BIN (widget);
409   hb = GTK_HANDLE_BOX (widget);
410
411   if (event != NULL)
412     area = &event->area;
413
414   for (x = 1; x < DRAG_HANDLE_SIZE; x += 3)
415     gtk_draw_vline (widget->style,
416                     widget->window,
417                     GTK_WIDGET_STATE (widget),
418                     0, hb->is_onroot ? hb->real_requisition.height : widget->allocation.height,
419                     x);
420
421   if (hb->is_onroot)
422     gtk_draw_shadow (widget->style,
423                      widget->window,
424                      GTK_WIDGET_STATE (widget),
425                      GTK_SHADOW_OUT,
426                      0, 0,
427                      hb->real_requisition.width,
428                      hb->real_requisition.height);
429   else
430     gtk_draw_shadow (widget->style,
431                      widget->window,
432                      GTK_WIDGET_STATE (widget),
433                      GTK_SHADOW_OUT,
434                      0, 0,
435                      widget->allocation.width,
436                      widget->allocation.height);
437
438   if (bin->child)
439     {
440       if (event == NULL) /* we were called from draw() */
441         {
442           if (gtk_widget_intersect (bin->child, area, &child_area))
443             gtk_widget_draw (bin->child, &child_area);
444         }
445       else /* we were called from expose() */
446         {
447           child_event = *event;
448           
449           if (GTK_WIDGET_NO_WINDOW (bin->child)
450               && gtk_widget_intersect (bin->child, &event->area, &child_event.area))
451             gtk_widget_event (bin->child, (GdkEvent *) &child_event);
452         }
453     }
454 }
455
456 static void
457 gtk_handle_box_draw (GtkWidget    *widget,
458                      GdkRectangle *area)
459 {
460   GdkRectangle child_area;
461   GtkHandleBox *hb;
462
463   g_return_if_fail (widget != NULL);
464   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
465   g_return_if_fail (area != NULL);
466
467   hb = GTK_HANDLE_BOX (widget);
468
469   if (GTK_WIDGET_DRAWABLE (widget))
470     {
471       if (hb->is_onroot)
472         {
473           /* The area parameter does not make sense in this case, so we
474            * repaint everything.
475            */
476
477           gtk_handle_box_draw_ghost (widget);
478
479           child_area.x = 0;
480           child_area.y = 0;
481           child_area.width = hb->real_requisition.width;
482           child_area.height = hb->real_requisition.height;
483
484           gtk_handle_box_paint (widget, NULL, &child_area);
485         }
486       else
487         gtk_handle_box_paint (widget, NULL, area);
488     }
489 }
490
491 static gint
492 gtk_handle_box_expose (GtkWidget      *widget,
493                        GdkEventExpose *event)
494 {
495   GtkHandleBox *hb;
496
497   g_return_val_if_fail (widget != NULL, FALSE);
498   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
499   g_return_val_if_fail (event != NULL, FALSE);
500
501   if (GTK_WIDGET_DRAWABLE (widget))
502     {
503       hb = GTK_HANDLE_BOX (widget);
504
505       if (event->window == hb->steady_window)
506         gtk_handle_box_draw_ghost (widget);
507       else if (event->window == widget->window)
508         gtk_handle_box_paint (widget, event, NULL);
509     }
510
511   return FALSE;
512 }
513
514 static gint
515 gtk_handle_box_button_changed (GtkWidget      *widget,
516                                GdkEventButton *event)
517 {
518   GtkHandleBox *hb;
519
520   g_return_val_if_fail (widget != NULL, FALSE);
521   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
522   g_return_val_if_fail (event != NULL, FALSE);
523
524   hb = GTK_HANDLE_BOX (widget);
525
526   if (event->button == 1)
527     {
528       if ((event->type == GDK_BUTTON_PRESS) && (event->x < DRAG_HANDLE_SIZE))
529         {
530           hb->dragoff_x = event->x;
531           hb->dragoff_y = event->y;
532
533           gdk_window_get_origin (hb->steady_window, &hb->steady_x, &hb->steady_y);
534
535           hb->is_being_dragged = TRUE;
536
537           gdk_flush();
538           gtk_grab_add (widget);
539           while (gdk_pointer_grab (widget->window,
540                                    FALSE,
541                                    (GDK_BUTTON1_MOTION_MASK
542                                     | GDK_POINTER_MOTION_HINT_MASK
543                                     | GDK_BUTTON_RELEASE_MASK),
544                                    NULL,
545                                    hb->fleur_cursor,
546                                    GDK_CURRENT_TIME) != 0); /* wait for success */
547         }
548       else if ((event->type == GDK_BUTTON_RELEASE) && hb->is_being_dragged)
549         {
550           gdk_pointer_ungrab (GDK_CURRENT_TIME);
551           gtk_grab_remove (widget);
552           hb->is_being_dragged = FALSE;
553         }
554     }
555   return TRUE;
556 }
557
558 static gint
559 gtk_handle_box_motion (GtkWidget      *widget,
560                        GdkEventMotion *event)
561 {
562   GtkHandleBox *hb;
563   gint newx, newy;
564   gint ox, oy;
565
566   g_return_val_if_fail (widget != NULL, FALSE);
567   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
568   g_return_val_if_fail (event != NULL, FALSE);
569
570   hb = GTK_HANDLE_BOX (widget);
571
572   if (event->is_hint)
573     {
574       gdk_window_get_origin (widget->window, &ox, &oy);
575       gdk_window_get_pointer (widget->window, &newx, &newy, NULL);
576       newx += ox;
577       newy += oy;
578     }
579   else
580     {
581       newx = event->x_root;
582       newy = event->y_root;
583     }
584
585   newx -= hb->dragoff_x;
586   newy -= hb->dragoff_y;
587
588   if (hb->is_being_dragged)
589     {
590       if ((abs (hb->steady_x - newx) < SNAP_TOLERANCE)
591           && (abs (hb->steady_y - newy) < SNAP_TOLERANCE))
592         {
593           if (hb->is_onroot)
594             {
595               hb->is_onroot = FALSE;
596
597               gdk_pointer_ungrab (GDK_CURRENT_TIME);
598
599               gdk_window_reparent (widget->window, hb->steady_window, 0, 0);
600
601               gtk_widget_hide (hb->float_window);
602
603               while (gdk_pointer_grab (widget->window,
604                                        FALSE,
605                                        (GDK_BUTTON1_MOTION_MASK
606                                         | GDK_POINTER_MOTION_HINT_MASK
607                                         | GDK_BUTTON_RELEASE_MASK),
608                                        NULL,
609                                        hb->fleur_cursor,
610                                        GDK_CURRENT_TIME) != 0); /* wait for success */
611
612               gtk_widget_queue_resize (widget);
613             }
614         }
615       else
616         {
617           if (hb->is_onroot)
618             {
619               gtk_widget_set_uposition (hb->float_window, newx, newy);
620               gdk_window_raise (hb->float_window->window);
621             }
622           else
623             {
624               hb->is_onroot = TRUE;
625
626               gdk_pointer_ungrab (GDK_CURRENT_TIME);
627
628               if (!GTK_WIDGET_REALIZED (hb->float_window))
629                 gtk_widget_realize (hb->float_window);
630
631               gtk_widget_set_uposition (hb->float_window, newx, newy);
632               gdk_window_reparent (widget->window, hb->float_window->window, 0, 0);
633               gtk_widget_show (hb->float_window);
634
635               while (gdk_pointer_grab (widget->window,
636                                        FALSE,
637                                        (GDK_BUTTON1_MOTION_MASK
638                                         | GDK_POINTER_MOTION_HINT_MASK
639                                         | GDK_BUTTON_RELEASE_MASK),
640                                        NULL,
641                                        hb->fleur_cursor,
642                                        GDK_CURRENT_TIME) != 0); /* wait for success */
643
644               gtk_widget_queue_resize (widget);
645             }
646         }
647     }
648
649   return TRUE;
650 }
651
652 static gint
653 gtk_handle_box_delete_float (GtkWidget *widget,
654                              GdkEvent  *event,
655                              gpointer   data)
656 {
657   GtkHandleBox *hb;
658
659   g_return_val_if_fail (widget != NULL, FALSE);
660   g_return_val_if_fail (event != NULL, FALSE);
661   g_return_val_if_fail (data != NULL, FALSE);
662   g_return_val_if_fail (GTK_IS_HANDLE_BOX (data), FALSE);
663
664   hb = GTK_HANDLE_BOX (data);
665
666   hb->is_onroot = FALSE;
667
668   gdk_window_reparent (GTK_WIDGET (hb)->window, hb->steady_window, 0, 0);
669   gtk_widget_hide (hb->float_window);
670   gtk_widget_queue_resize (GTK_WIDGET (hb));
671
672   return FALSE;
673 }