]> Pileus Git - ~andy/gtk/blob - gtk/gtkhandlebox.c
The auto_shrink policy of the floating window is now set to TRUE. This
[~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
151   if (GTK_OBJECT_CLASS (parent_class)->destroy)
152     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
153 }
154
155 static void
156 gtk_handle_box_map (GtkWidget *widget)
157 {
158   GtkBin *bin;
159   GtkHandleBox *hb;
160
161   g_return_if_fail (widget != NULL);
162   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
163
164   GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
165
166   bin = GTK_BIN (widget);
167   hb = GTK_HANDLE_BOX (widget);
168
169   gdk_window_show (hb->steady_window);
170   gdk_window_show (widget->window);
171
172   if (bin->child
173       && GTK_WIDGET_VISIBLE (bin->child)
174       && !GTK_WIDGET_MAPPED (bin->child))
175     gtk_widget_map (bin->child);
176 }
177
178 static void
179 gtk_handle_box_unmap (GtkWidget *widget)
180 {
181   GtkHandleBox *hb;
182
183   g_return_if_fail (widget != NULL);
184   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
185
186   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
187
188   hb = GTK_HANDLE_BOX (widget);
189
190   gdk_window_hide (widget->window);
191   gdk_window_hide (hb->steady_window);
192 }
193
194 static void
195 gtk_handle_box_realize (GtkWidget *widget)
196 {
197   GdkWindowAttr attributes;
198   gint attributes_mask;
199   GtkHandleBox *hb;
200
201   g_return_if_fail (widget != NULL);
202   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
203
204   hb = GTK_HANDLE_BOX (widget);
205
206   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
207
208   /* FIXME: we need a property that would tell the window manager not
209    * to put decoration on this window.  This is not part of the ICCCM,
210    * so we'll have to define our own (a la KWM) and hack some window
211    * managers to support it.
212    */
213
214   hb->float_window = gtk_window_new (GTK_WINDOW_DIALOG);
215   gtk_window_set_policy (GTK_WINDOW (hb->float_window), FALSE, FALSE, TRUE);
216   gtk_container_border_width (GTK_CONTAINER (hb->float_window), 0);
217   gtk_signal_connect (GTK_OBJECT (hb->float_window), "delete_event",
218                       (GtkSignalFunc) gtk_handle_box_delete_float,
219                       hb);
220   
221   attributes.x = widget->allocation.x;
222   attributes.y = widget->allocation.y;
223   attributes.width = widget->allocation.width;
224   attributes.height = widget->allocation.height;
225   attributes.window_type = GDK_WINDOW_CHILD;
226   attributes.wclass = GDK_INPUT_OUTPUT;
227   attributes.visual = gtk_widget_get_visual (widget);
228   attributes.colormap = gtk_widget_get_colormap (widget);
229   attributes.event_mask = (gtk_widget_get_events (widget)
230                            | GDK_EXPOSURE_MASK);
231
232   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
233
234   hb->steady_window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
235   gdk_window_set_user_data (hb->steady_window, widget);
236
237   attributes.x = 0;
238   attributes.y = 0;
239   attributes.event_mask |= (GDK_BUTTON1_MOTION_MASK
240                             | GDK_POINTER_MOTION_HINT_MASK
241                             | GDK_BUTTON_PRESS_MASK
242                             | GDK_BUTTON_RELEASE_MASK);
243
244   widget->window = gdk_window_new (hb->steady_window, &attributes, attributes_mask);
245   gdk_window_set_user_data (widget->window, widget);
246
247   widget->style = gtk_style_attach (widget->style, widget->window);
248   gtk_style_set_background (widget->style, hb->steady_window, GTK_STATE_NORMAL);
249   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
250 }
251
252 static void
253 gtk_handle_box_unrealize (GtkWidget *widget)
254 {
255   GtkHandleBox *hb;
256
257   g_return_if_fail (widget != NULL);
258   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
259
260   GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED | GTK_MAPPED);
261
262   gtk_style_detach (widget->style);
263
264   hb = GTK_HANDLE_BOX (widget);
265
266   if (widget->window)
267     gdk_window_destroy (widget->window);
268
269   if (hb->steady_window)
270     gdk_window_destroy (hb->steady_window);
271
272   hb->steady_window = NULL;
273   widget->window = NULL;
274
275   /* FIXME: do we have to unref the float_window before destroying it? */
276
277   gtk_widget_destroy (hb->float_window);
278   hb->float_window = NULL;
279 }
280
281 static void
282 gtk_handle_box_size_request (GtkWidget      *widget,
283                              GtkRequisition *requisition)
284 {
285   GtkBin *bin;
286   GtkHandleBox *hb;
287
288   g_return_if_fail (widget != NULL);
289   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
290   g_return_if_fail (requisition != NULL);
291
292   bin = GTK_BIN (widget);
293   hb = GTK_HANDLE_BOX (widget);
294
295   requisition->width = DRAG_HANDLE_SIZE + GTK_CONTAINER (widget)->border_width * 2;
296   requisition->height = GTK_CONTAINER (widget)->border_width * 2;
297
298   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
299     {
300       gtk_widget_size_request (bin->child, &bin->child->requisition);
301
302       requisition->width += bin->child->requisition.width;
303       requisition->height += bin->child->requisition.height;
304     }
305
306   hb->real_requisition = *requisition;
307   if (hb->is_onroot)
308       requisition->height = GHOST_HEIGHT;
309   /* FIXME: Should also set requisition->width to a small value? */
310 }
311
312 static void
313 gtk_handle_box_size_allocate (GtkWidget     *widget,
314                               GtkAllocation *allocation)
315 {
316   GtkBin *bin;
317   GtkAllocation child_allocation;
318   GtkHandleBox *hb;
319   gint border_width;
320
321   g_return_if_fail (widget != NULL);
322   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
323   g_return_if_fail (allocation != NULL);
324
325   widget->allocation = *allocation;
326   bin = GTK_BIN (widget);
327   hb = GTK_HANDLE_BOX (widget);
328
329   border_width = GTK_CONTAINER (widget)->border_width;
330
331   if (GTK_WIDGET_REALIZED (widget))
332     {
333       if (!hb->is_onroot)
334         {
335           gdk_window_move_resize (hb->steady_window,
336                                   allocation->x + border_width,
337                                   allocation->y + border_width,
338                                   allocation->width - border_width * 2,
339                                   allocation->height - border_width * 2);
340           gdk_window_move_resize (widget->window,
341                                   0,
342                                   0,
343                                   allocation->width - border_width * 2,
344                                   allocation->height - border_width * 2);
345         }
346       else
347         {
348           gtk_widget_set_usize (hb->float_window,
349                                 hb->real_requisition.width,
350                                 hb->real_requisition.height);
351           gdk_window_resize (widget->window,
352                              hb->real_requisition.width,
353                              hb->real_requisition.height);
354           gdk_window_move_resize (hb->steady_window,
355                                   allocation->x + border_width,
356                                   allocation->y + border_width,
357                                   allocation->width - border_width * 2,
358                                   GHOST_HEIGHT);
359         }
360     }
361
362   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
363     {
364       child_allocation.x = DRAG_HANDLE_SIZE;
365       child_allocation.y = 0;
366
367       if (hb->is_onroot)
368         {
369           child_allocation.width = hb->real_requisition.width - DRAG_HANDLE_SIZE;
370           child_allocation.height = hb->real_requisition.height;
371         }
372       else
373         {
374           child_allocation.width = allocation->width - DRAG_HANDLE_SIZE - border_width * 2;
375           child_allocation.height = allocation->height - border_width * 2;
376         }
377
378       gtk_widget_size_allocate (bin->child, &child_allocation);
379     }
380 }
381
382 static void
383 gtk_handle_box_draw_ghost (GtkWidget *widget)
384 {
385   gtk_draw_hline (widget->style,
386                   GTK_HANDLE_BOX (widget)->steady_window,
387                   GTK_WIDGET_STATE (widget),
388                   0,
389                   widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2,
390                   0);
391 }
392
393 static void
394 gtk_handle_box_paint (GtkWidget      *widget,
395                       GdkEventExpose *event,
396                       GdkRectangle   *area)
397 {
398   GtkBin *bin;
399   GtkHandleBox *hb;
400   GdkRectangle child_area;
401   GdkEventExpose child_event;
402   gint x;
403
404   bin = GTK_BIN (widget);
405   hb = GTK_HANDLE_BOX (widget);
406
407   if (event != NULL)
408     area = &event->area;
409
410   for (x = 1; x < DRAG_HANDLE_SIZE; x += 3)
411     gtk_draw_vline (widget->style,
412                     widget->window,
413                     GTK_WIDGET_STATE (widget),
414                     0, hb->is_onroot ? hb->real_requisition.height : widget->allocation.height,
415                     x);
416
417   if (hb->is_onroot)
418     gtk_draw_shadow (widget->style,
419                      widget->window,
420                      GTK_WIDGET_STATE (widget),
421                      GTK_SHADOW_OUT,
422                      0, 0,
423                      hb->real_requisition.width,
424                      hb->real_requisition.height);
425   else
426     gtk_draw_shadow (widget->style,
427                      widget->window,
428                      GTK_WIDGET_STATE (widget),
429                      GTK_SHADOW_OUT,
430                      0, 0,
431                      widget->allocation.width,
432                      widget->allocation.height);
433
434   if (bin->child)
435     {
436       if (event == NULL) /* we were called from draw() */
437         {
438           if (gtk_widget_intersect (bin->child, area, &child_area))
439             gtk_widget_draw (bin->child, &child_area);
440         }
441       else /* we were called from expose() */
442         {
443           child_event = *event;
444           
445           if (GTK_WIDGET_NO_WINDOW (bin->child)
446               && gtk_widget_intersect (bin->child, &event->area, &child_event.area))
447             gtk_widget_event (bin->child, (GdkEvent *) &child_event);
448         }
449     }
450 }
451
452 static void
453 gtk_handle_box_draw (GtkWidget    *widget,
454                      GdkRectangle *area)
455 {
456   GdkRectangle child_area;
457   GtkHandleBox *hb;
458
459   g_return_if_fail (widget != NULL);
460   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
461   g_return_if_fail (area != NULL);
462
463   hb = GTK_HANDLE_BOX (widget);
464
465   if (GTK_WIDGET_DRAWABLE (widget))
466     {
467       if (hb->is_onroot)
468         {
469           /* The area parameter does not make sense in this case, so we
470            * repaint everything.
471            */
472
473           gtk_handle_box_draw_ghost (widget);
474
475           child_area.x = 0;
476           child_area.y = 0;
477           child_area.width = hb->real_requisition.width;
478           child_area.height = hb->real_requisition.height;
479
480           gtk_handle_box_paint (widget, NULL, &child_area);
481         }
482       else
483         gtk_handle_box_paint (widget, NULL, area);
484     }
485 }
486
487 static gint
488 gtk_handle_box_expose (GtkWidget      *widget,
489                        GdkEventExpose *event)
490 {
491   GtkHandleBox *hb;
492
493   g_return_val_if_fail (widget != NULL, FALSE);
494   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
495   g_return_val_if_fail (event != NULL, FALSE);
496
497   if (GTK_WIDGET_DRAWABLE (widget))
498     {
499       hb = GTK_HANDLE_BOX (widget);
500
501       if (event->window == hb->steady_window)
502         gtk_handle_box_draw_ghost (widget);
503       else if (event->window == widget->window)
504         gtk_handle_box_paint (widget, event, NULL);
505     }
506
507   return FALSE;
508 }
509
510 static gint
511 gtk_handle_box_button_changed (GtkWidget      *widget,
512                                GdkEventButton *event)
513 {
514   GtkHandleBox *hb;
515
516   g_return_val_if_fail (widget != NULL, FALSE);
517   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
518   g_return_val_if_fail (event != NULL, FALSE);
519
520   hb = GTK_HANDLE_BOX (widget);
521
522   if (event->button == 1)
523     {
524       if ((event->type == GDK_BUTTON_PRESS) && (event->x < DRAG_HANDLE_SIZE))
525         {
526           hb->dragoff_x = event->x;
527           hb->dragoff_y = event->y;
528
529           gdk_window_get_origin (hb->steady_window, &hb->steady_x, &hb->steady_y);
530
531           hb->is_being_dragged = TRUE;
532
533           gdk_flush();
534           gtk_grab_add (widget);
535           while (gdk_pointer_grab (widget->window,
536                                    FALSE,
537                                    (GDK_BUTTON1_MOTION_MASK
538                                     | GDK_POINTER_MOTION_HINT_MASK
539                                     | GDK_BUTTON_RELEASE_MASK),
540                                    NULL,
541                                    hb->fleur_cursor,
542                                    GDK_CURRENT_TIME) != 0); /* wait for success */
543         }
544       else if ((event->type == GDK_BUTTON_RELEASE) && hb->is_being_dragged)
545         {
546           gdk_pointer_ungrab (GDK_CURRENT_TIME);
547           gtk_grab_remove (widget);
548           hb->is_being_dragged = FALSE;
549         }
550     }
551   return TRUE;
552 }
553
554 static gint
555 gtk_handle_box_motion (GtkWidget      *widget,
556                        GdkEventMotion *event)
557 {
558   GtkHandleBox *hb;
559   gint newx, newy;
560   gint ox, oy;
561
562   g_return_val_if_fail (widget != NULL, FALSE);
563   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
564   g_return_val_if_fail (event != NULL, FALSE);
565
566   hb = GTK_HANDLE_BOX (widget);
567
568   if (event->is_hint)
569     {
570       gdk_window_get_origin (widget->window, &ox, &oy);
571       gdk_window_get_pointer (widget->window, &newx, &newy, NULL);
572       newx += ox;
573       newy += oy;
574     }
575   else
576     {
577       newx = event->x_root;
578       newy = event->y_root;
579     }
580
581   newx -= hb->dragoff_x;
582   newy -= hb->dragoff_y;
583
584   if (hb->is_being_dragged)
585     {
586       newx = event->x_root - hb->dragoff_x;
587       newy = event->y_root - hb->dragoff_y;
588
589       if ((abs (hb->steady_x - newx) < SNAP_TOLERANCE)
590           && (abs (hb->steady_y - newy) < SNAP_TOLERANCE))
591         {
592           if (hb->is_onroot)
593             {
594               hb->is_onroot = FALSE;
595
596               gdk_pointer_ungrab (GDK_CURRENT_TIME);
597
598               gdk_window_reparent (widget->window, hb->steady_window, 0, 0);
599
600               gtk_widget_hide (hb->float_window);
601
602               while (gdk_pointer_grab (widget->window,
603                                        FALSE,
604                                        (GDK_BUTTON1_MOTION_MASK
605                                         | GDK_POINTER_MOTION_HINT_MASK
606                                         | GDK_BUTTON_RELEASE_MASK),
607                                        NULL,
608                                        hb->fleur_cursor,
609                                        GDK_CURRENT_TIME) != 0); /* wait for success */
610
611               gtk_widget_queue_resize (widget);
612             }
613         }
614       else
615         {
616           if (hb->is_onroot)
617             {
618               gtk_widget_set_uposition (hb->float_window, newx, newy);
619               gdk_window_raise (hb->float_window->window);
620             }
621           else
622             {
623               hb->is_onroot = TRUE;
624
625               gdk_pointer_ungrab (GDK_CURRENT_TIME);
626
627               if (!GTK_WIDGET_REALIZED (hb->float_window))
628                 gtk_widget_realize (hb->float_window);
629
630               gtk_widget_set_uposition (hb->float_window, newx, newy);
631               gdk_window_reparent (widget->window, hb->float_window->window, 0, 0);
632               gtk_widget_show (hb->float_window);
633
634               while (gdk_pointer_grab (widget->window,
635                                        FALSE,
636                                        (GDK_BUTTON1_MOTION_MASK
637                                         | GDK_POINTER_MOTION_HINT_MASK
638                                         | GDK_BUTTON_RELEASE_MASK),
639                                        NULL,
640                                        hb->fleur_cursor,
641                                        GDK_CURRENT_TIME) != 0); /* wait for success */
642
643               gtk_widget_queue_resize (widget);
644             }
645         }
646     }
647
648   return TRUE;
649 }
650
651 static gint
652 gtk_handle_box_delete_float (GtkWidget *widget,
653                              GdkEvent  *event,
654                              gpointer   data)
655 {
656   GtkHandleBox *hb;
657
658   g_return_val_if_fail (widget != NULL, FALSE);
659   g_return_val_if_fail (event != NULL, FALSE);
660   g_return_val_if_fail (data != NULL, FALSE);
661   g_return_val_if_fail (GTK_IS_HANDLE_BOX (data), FALSE);
662
663   hb = GTK_HANDLE_BOX (data);
664
665   hb->is_onroot = FALSE;
666
667   gdk_window_reparent (GTK_WIDGET (hb)->window, hb->steady_window, 0, 0);
668   gtk_widget_hide (hb->float_window);
669   gtk_widget_queue_resize (GTK_WIDGET (hb));
670
671   return FALSE;
672 }