]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtknotebook.c
Replace a lot of idle and timeout calls by the new gdk_threads api.
[~andy/gtk] / gtk / gtknotebook.c
index fb823ef34dd0d7fc37a31f9fe03655bba1105a10..1448a52073010fdddfbfc8e0443a2ce2474472ee 100644 (file)
 #include "gtkmarshalers.h"
 #include "gtkbindings.h"
 #include "gtkprivate.h"
-#include "gtkalias.h"
 #include "gtkdnd.h"
+#include "gtkalias.h"
 
 #define SCROLL_DELAY_FACTOR   5
 #define SCROLL_THRESHOLD      12
 #define DND_THRESHOLD_MULTIPLIER 4
+#define FRAMES_PER_SECOND     45
+#define MSECS_BETWEEN_UPDATES (1000 / FRAMES_PER_SECOND)
 
 enum {
   SWITCH_PAGE,
@@ -161,6 +163,13 @@ struct _GtkNotebookPrivate
   guint dnd_timer;
   guint switch_tab_timer;
 
+  gint  drag_begin_x;
+  gint  drag_begin_y;
+
+  gint  drag_offset_x;
+  gint  drag_offset_y;
+
+  GtkWidget *dnd_window;
   GtkTargetList *source_targets;
   GtkNotebookDragOperation operation;
   GdkWindow *drag_window;
@@ -168,8 +177,11 @@ struct _GtkNotebookPrivate
   gint drag_window_y;
   GtkNotebookPage *detached_tab;
 
-  gboolean during_detach : 1;
-  gboolean has_scrolled  : 1;
+  guint32 timestamp;
+
+  guint during_reorder : 1;
+  guint during_detach  : 1;
+  guint has_scrolled   : 1;
 };
 
 static const GtkTargetEntry notebook_source_targets [] = {
@@ -231,8 +243,6 @@ static gint gtk_notebook_button_press        (GtkWidget        *widget,
 static gint gtk_notebook_button_release      (GtkWidget        *widget,
                                              GdkEventButton   *event);
 static gboolean gtk_notebook_popup_menu      (GtkWidget        *widget);
-static gint gtk_notebook_enter_notify        (GtkWidget        *widget,
-                                             GdkEventCrossing *event);
 static gint gtk_notebook_leave_notify        (GtkWidget        *widget,
                                              GdkEventCrossing *event);
 static gint gtk_notebook_motion_notify       (GtkWidget        *widget,
@@ -254,6 +264,8 @@ static void gtk_notebook_style_set           (GtkWidget        *widget,
 /*** Drag and drop Methods ***/
 static void gtk_notebook_drag_begin          (GtkWidget        *widget,
                                              GdkDragContext   *context);
+static void gtk_notebook_drag_end            (GtkWidget        *widget,
+                                             GdkDragContext   *context);
 static gboolean gtk_notebook_drag_motion     (GtkWidget        *widget,
                                              GdkDragContext   *context,
                                              gint              x,
@@ -397,10 +409,11 @@ static void stop_scrolling (GtkNotebook *notebook);
 
 static GtkNotebookWindowCreationFunc window_creation_hook = NULL;
 static gpointer window_creation_hook_data;
+static GDestroyNotify window_creation_hook_destroy = NULL;
 
 static guint notebook_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (GtkNotebook, gtk_notebook, GTK_TYPE_CONTAINER);
+G_DEFINE_TYPE (GtkNotebook, gtk_notebook, GTK_TYPE_CONTAINER)
 
 static void
 add_tab_bindings (GtkBindingSet    *binding_set,
@@ -472,7 +485,6 @@ gtk_notebook_class_init (GtkNotebookClass *class)
   widget_class->button_press_event = gtk_notebook_button_press;
   widget_class->button_release_event = gtk_notebook_button_release;
   widget_class->popup_menu = gtk_notebook_popup_menu;
-  widget_class->enter_notify_event = gtk_notebook_enter_notify;
   widget_class->leave_notify_event = gtk_notebook_leave_notify;
   widget_class->motion_notify_event = gtk_notebook_motion_notify;
   widget_class->grab_notify = gtk_notebook_grab_notify;
@@ -482,6 +494,7 @@ gtk_notebook_class_init (GtkNotebookClass *class)
   widget_class->focus = gtk_notebook_focus;
   widget_class->style_set = gtk_notebook_style_set;
   widget_class->drag_begin = gtk_notebook_drag_begin;
+  widget_class->drag_end = gtk_notebook_drag_end;
   widget_class->drag_motion = gtk_notebook_drag_motion;
   widget_class->drag_leave = gtk_notebook_drag_leave;
   widget_class->drag_drop = gtk_notebook_drag_drop;
@@ -1053,14 +1066,33 @@ gtk_notebook_change_current_page (GtkNotebook *notebook,
 
   while (offset != 0)
     {
-      current = gtk_notebook_search_page (notebook, current, offset < 0 ? STEP_PREV : STEP_NEXT, TRUE);
+      current = gtk_notebook_search_page (notebook, current,
+                                          offset < 0 ? STEP_PREV : STEP_NEXT,
+                                          TRUE);
+
+      if (!current)
+        {
+          gboolean wrap_around;
+
+          g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+                        "gtk-keynav-wrap-around", &wrap_around,
+                        NULL);
+
+          if (wrap_around)
+            current = gtk_notebook_search_page (notebook, NULL,
+                                                offset < 0 ? STEP_PREV : STEP_NEXT,
+                                                TRUE);
+          else
+            break;
+        }
+
       offset += offset < 0 ? 1 : -1;
     }
 
   if (current)
     gtk_notebook_switch_page (notebook, current->data, -1);
   else
-    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
+    gtk_widget_error_bell (GTK_WIDGET (notebook));
 }
 
 static GtkDirectionType
@@ -1218,6 +1250,9 @@ gtk_notebook_reorder_tab (GtkNotebook      *notebook,
   GList *last, *child;
   gint page_num;
 
+  if (!gtk_widget_is_focus (GTK_WIDGET (notebook)))
+    return;
+
   if (!notebook->cur_page ||
       !notebook->cur_page->reorderable)
     return;
@@ -1426,7 +1461,6 @@ gtk_notebook_get_property (GObject         *object,
  * gtk_notebook_button_press
  * gtk_notebook_button_release
  * gtk_notebook_popup_menu
- * gtk_notebook_enter_notify
  * gtk_notebook_leave_notify
  * gtk_notebook_motion_notify
  * gtk_notebook_focus_in
@@ -1434,6 +1468,7 @@ gtk_notebook_get_property (GObject         *object,
  * gtk_notebook_draw_focus
  * gtk_notebook_style_set
  * gtk_notebook_drag_begin
+ * gtk_notebook_drag_end
  * gtk_notebook_drag_motion
  * gtk_notebook_drag_drop
  * gtk_notebook_drag_data_get
@@ -1580,7 +1615,7 @@ gtk_notebook_realize (GtkWidget *widget)
   attributes.event_mask = gtk_widget_get_events (widget);
   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
                            GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK |
-                           GDK_BUTTON1_MOTION_MASK |
+                           GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK |
                            GDK_SCROLL_MASK);
   attributes_mask = GDK_WA_X | GDK_WA_Y;
 
@@ -2325,21 +2360,29 @@ gtk_notebook_button_press (GtkWidget      *widget,
       page = tab->data;
       page_changed = page != notebook->cur_page;
       was_focus = gtk_widget_is_focus (widget);
-         
+
       gtk_notebook_switch_focus_tab (notebook, tab);
       gtk_widget_grab_focus (widget);
-         
+
       if (page_changed && !was_focus)
        gtk_widget_child_focus (page->child, GTK_DIR_TAB_FORWARD);
-         
+
       /* save press to possibly begin a drag */
       if (page->reorderable || page->detachable)
        {
          priv->during_detach = FALSE;
+         priv->during_reorder = FALSE;
          priv->pressed_button = event->button;
 
-         priv->mouse_x = x - widget->allocation.x;
-         priv->mouse_y = y - widget->allocation.y;
+         gdk_window_get_pointer (widget->window,
+                                 &priv->mouse_x,
+                                 &priv->mouse_y,
+                                 NULL);
+
+         priv->drag_begin_x = priv->mouse_x;
+         priv->drag_begin_y = priv->mouse_y;
+         priv->drag_offset_x = priv->drag_begin_x - page->allocation.x;
+         priv->drag_offset_y = priv->drag_begin_y - page->allocation.y;
        }
     }
 
@@ -2424,8 +2467,8 @@ get_drop_position (GtkNotebook *notebook,
   gint x, y;
 
   priv = GTK_NOTEBOOK_GET_PRIVATE (notebook);
-  x = priv->mouse_x + GTK_WIDGET (notebook)->allocation.x;
-  y = priv->mouse_y + GTK_WIDGET (notebook)->allocation.y;
+  x = priv->mouse_x;
+  y = priv->mouse_y;
 
   is_rtl = gtk_widget_get_direction ((GtkWidget *) notebook) == GTK_TEXT_DIR_RTL;
   children = notebook->children;
@@ -2555,44 +2598,38 @@ hide_drag_window (GtkNotebook        *notebook,
     gdk_window_hide (priv->drag_window);
 }
 
-static gint
-gtk_notebook_button_release (GtkWidget      *widget,
-                            GdkEventButton *event)
+static void
+gtk_notebook_stop_reorder (GtkNotebook *notebook)
 {
-  GtkNotebook *notebook;
   GtkNotebookPrivate *priv;
   GtkNotebookPage *page;
-  GList *element;
-  gint old_page_num, page_num;
-
-  g_return_val_if_fail (GTK_IS_NOTEBOOK (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-
-  if (event->type != GDK_BUTTON_RELEASE)
-    return FALSE;
 
-  notebook = GTK_NOTEBOOK (widget);
   priv = GTK_NOTEBOOK_GET_PRIVATE (notebook);
   page = notebook->cur_page;
 
-  if (page->reorderable &&
-      event->button == priv->pressed_button)
-    {
-      priv->pressed_button = -1;
+  if (!page)
+    return;
+
+  priv->pressed_button = -1;
 
-      if (!priv->during_detach)
+  if (page->reorderable || page->detachable)
+    {
+      if (priv->during_reorder)
        {
+         gint old_page_num, page_num;
+         GList *element;
+
          element = get_drop_position (notebook, page->pack);
          old_page_num = g_list_position (notebook->children, notebook->focus_tab);
          page_num = reorder_tab (notebook, element, notebook->focus_tab);
 
-         if (priv->has_scrolled ||
-             old_page_num != page_num)
+         if (priv->has_scrolled || old_page_num != page_num)
            g_signal_emit (notebook,
                           notebook_signals[PAGE_REORDERED], 0,
                           page->child, page_num);
 
          priv->has_scrolled = FALSE;
+          priv->during_reorder = FALSE; 
        }
 
       hide_drag_window (notebook, priv, page);
@@ -2606,43 +2643,38 @@ gtk_notebook_button_release (GtkWidget      *widget,
          priv->dnd_timer = 0;
        }
     }
-
-  if (event->button == notebook->button)
-    {
-      stop_scrolling (notebook);
-      return TRUE;
-    }
-  else
-    return FALSE;
 }
 
 static gint
-gtk_notebook_enter_notify (GtkWidget        *widget,
-                          GdkEventCrossing *event)
+gtk_notebook_button_release (GtkWidget      *widget,
+                            GdkEventButton *event)
 {
   GtkNotebook *notebook;
-  GtkNotebookArrow arrow;
-  gint x, y;
+  GtkNotebookPrivate *priv;
+  GtkNotebookPage *page;
 
   g_return_val_if_fail (GTK_IS_NOTEBOOK (widget), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
 
-  notebook = GTK_NOTEBOOK (widget);
-
-  if (!get_widget_coordinates (widget, (GdkEvent *)event, &x, &y))
+  if (event->type != GDK_BUTTON_RELEASE)
     return FALSE;
 
-  arrow = gtk_notebook_get_arrow (notebook, x, y);
+  notebook = GTK_NOTEBOOK (widget);
+  priv = GTK_NOTEBOOK_GET_PRIVATE (notebook);
+  page = notebook->cur_page;
 
-  if (arrow != notebook->in_child)
-    {
-      notebook->in_child = arrow;
-      gtk_notebook_redraw_arrows (notebook);
+  if (!priv->during_detach &&
+      page->reorderable &&
+      event->button == priv->pressed_button)
+    gtk_notebook_stop_reorder (notebook);
 
+  if (event->button == notebook->button)
+    {
+      stop_scrolling (notebook);
       return TRUE;
     }
-
-  return TRUE;
+  else
+    return FALSE;
 }
 
 static gint
@@ -2678,20 +2710,26 @@ get_pointer_position (GtkNotebook *notebook)
   if (notebook->tab_pos == GTK_POS_TOP ||
       notebook->tab_pos == GTK_POS_BOTTOM)
     {
+      gint x;
+
       is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+      x = priv->mouse_x - widget->allocation.x;
 
-      if (priv->mouse_x > widget->allocation.width - 2 * container->border_width - SCROLL_THRESHOLD)
+      if (x > widget->allocation.width - 2 * container->border_width - SCROLL_THRESHOLD)
        return (is_rtl) ? POINTER_BEFORE : POINTER_AFTER;
-      else if (priv->mouse_x < SCROLL_THRESHOLD + container->border_width)
+      else if (x < SCROLL_THRESHOLD + container->border_width)
        return (is_rtl) ? POINTER_AFTER : POINTER_BEFORE;
       else
        return POINTER_BETWEEN;
     }
   else
     {
-      if (priv->mouse_y > widget->allocation.height - 2 * container->border_width - SCROLL_THRESHOLD)
+      gint y;
+
+      y = priv->mouse_y - widget->allocation.y;
+      if (y > widget->allocation.height - 2 * container->border_width - SCROLL_THRESHOLD)
        return POINTER_AFTER;
-      else if (priv->mouse_y < SCROLL_THRESHOLD + container->border_width)
+      else if (y < SCROLL_THRESHOLD + container->border_width)
        return POINTER_BEFORE;
       else
        return POINTER_BETWEEN;
@@ -2768,26 +2806,44 @@ gtk_notebook_motion_notify (GtkWidget      *widget,
   GtkNotebook *notebook = GTK_NOTEBOOK (widget);
   GtkNotebookPrivate *priv = GTK_NOTEBOOK_GET_PRIVATE (notebook);
   GtkNotebookPage *page;
+  GtkNotebookArrow arrow;
   GtkNotebookPointerPosition pointer_position;
   GtkSettings *settings;
   guint timeout;
 
-  if (priv->pressed_button == -1)
+  page = notebook->cur_page;
+
+  if (!page)
     return FALSE;
 
-  if (!notebook->cur_page)
+  if (!(event->state & GDK_BUTTON1_MASK) &&
+      priv->pressed_button != -1)
+    {
+      gtk_notebook_stop_reorder (notebook);
+      stop_scrolling (notebook);
+    }
+
+  if (event->time < priv->timestamp + MSECS_BETWEEN_UPDATES)
     return FALSE;
 
-  page = notebook->cur_page;
+  priv->timestamp = event->time;
+  gdk_window_get_pointer (widget->window,
+                         &priv->mouse_x,
+                         &priv->mouse_y,
+                         NULL);
 
-  get_widget_coordinates (widget, (GdkEvent*) event, &priv->mouse_x, &priv->mouse_y);
-  priv->mouse_x -= widget->allocation.x;
-  priv->mouse_y -= widget->allocation.y;
+  arrow = gtk_notebook_get_arrow (notebook, priv->mouse_x, priv->mouse_y);
+  if (arrow != notebook->in_child)
+    {
+      notebook->in_child = arrow;
+      gtk_notebook_redraw_arrows (notebook);
+    }
+
+  if (priv->pressed_button == -1)
+    return FALSE;
 
   if (page->detachable &&
-      check_threshold (notebook,
-                      priv->mouse_x + widget->allocation.x,
-                      priv->mouse_y + widget->allocation.y))
+      check_threshold (notebook, priv->mouse_x, priv->mouse_y))
     {
       priv->detached_tab = notebook->cur_page;
       priv->during_detach = TRUE;
@@ -2797,8 +2853,11 @@ gtk_notebook_motion_notify (GtkWidget      *widget,
       return TRUE;
     }
 
-  if (page->reorderable)
+  if (page->reorderable &&
+      (priv->during_reorder ||
+       gtk_drag_check_threshold (widget, priv->drag_begin_x, priv->drag_begin_y, priv->mouse_x, priv->mouse_y)))
     {
+      priv->during_reorder = TRUE;
       pointer_position = get_pointer_position (notebook);
 
       if (event->window == priv->drag_window &&
@@ -2812,8 +2871,8 @@ gtk_notebook_motion_notify (GtkWidget      *widget,
              settings = gtk_widget_get_settings (GTK_WIDGET (notebook));
              g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
 
-             priv->dnd_timer = g_timeout_add (timeout * SCROLL_DELAY_FACTOR,
-                                              (GSourceFunc) scroll_notebook_timer, 
+             priv->dnd_timer = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
+                                              scroll_notebook_timer, 
                                               (gpointer) notebook);
            }
        }
@@ -2832,8 +2891,8 @@ gtk_notebook_motion_notify (GtkWidget      *widget,
          /* the drag operation is beginning, create the window */
          if (priv->operation != DRAG_OPERATION_REORDER)
            {
-             show_drag_window (notebook, priv, page);
              priv->operation = DRAG_OPERATION_REORDER;
+             show_drag_window (notebook, priv, page);
            }
 
          gtk_notebook_pages_allocate (notebook);
@@ -2852,8 +2911,13 @@ static void
 gtk_notebook_grab_notify (GtkWidget *widget,
                          gboolean   was_grabbed)
 {
+  GtkNotebook *notebook = GTK_NOTEBOOK (widget);
+
   if (!was_grabbed)
-    stop_scrolling (GTK_NOTEBOOK (widget));
+    {
+      gtk_notebook_stop_reorder (notebook);
+      stop_scrolling (notebook);
+    }
 }
 
 static void
@@ -2969,8 +3033,7 @@ gtk_notebook_drag_begin (GtkWidget        *widget,
 {
   GtkNotebookPrivate *priv = GTK_NOTEBOOK_GET_PRIVATE (widget);
   GtkNotebook *notebook = (GtkNotebook*) widget;
-  GtkWidget *tab_label, *window;
-  gint width, height;
+  GtkWidget *tab_label;
 
   if (priv->dnd_timer)
     {
@@ -2978,25 +3041,41 @@ gtk_notebook_drag_begin (GtkWidget        *widget,
       priv->dnd_timer = 0;
     }
 
+  priv->operation = DRAG_OPERATION_DETACH;
+  gtk_notebook_pages_allocate (notebook);
+
   tab_label = priv->detached_tab->tab_label;
-  gdk_drawable_get_size (priv->drag_window, &width, &height);
 
   hide_drag_window (notebook, priv, notebook->cur_page);
   g_object_ref (tab_label);
   gtk_widget_unparent (tab_label);
 
-  window = gtk_window_new (GTK_WINDOW_POPUP);
-  gtk_container_add (GTK_CONTAINER (window), tab_label);
-  gtk_widget_set_size_request (window, width, height);
+  priv->dnd_window = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_container_add (GTK_CONTAINER (priv->dnd_window), tab_label);
+  gtk_widget_set_size_request (priv->dnd_window,
+                              priv->detached_tab->allocation.width,
+                              priv->detached_tab->allocation.height);
   g_object_unref (tab_label);
 
-  g_signal_connect (G_OBJECT (window), "expose-event",
+  g_signal_connect (G_OBJECT (priv->dnd_window), "expose-event",
                    G_CALLBACK (on_drag_icon_expose), notebook);
 
-  gtk_drag_set_icon_widget (context, window, -2, -2);
+  gtk_drag_set_icon_widget (context, priv->dnd_window, -2, -2);
+}
 
-  priv->operation = DRAG_OPERATION_DETACH;
-  gtk_notebook_pages_allocate (notebook);
+static void
+gtk_notebook_drag_end (GtkWidget      *widget,
+                      GdkDragContext *context)
+{
+  GtkNotebookPrivate *priv = GTK_NOTEBOOK_GET_PRIVATE (widget);
+
+  gtk_notebook_stop_reorder (GTK_NOTEBOOK (widget));
+
+  GTK_BIN (priv->dnd_window)->child = NULL;
+  gtk_widget_destroy (priv->dnd_window);
+  priv->dnd_window = NULL;
+
+  priv->operation = DRAG_OPERATION_NONE;
 }
 
 static gboolean
@@ -3049,6 +3128,7 @@ gtk_notebook_drag_motion (GtkWidget      *widget,
     {
       notebook->click_child = arrow;
       gtk_notebook_set_scroll_timer (notebook);
+      gdk_drag_status (context, 0, time);
       return TRUE;
     }
 
@@ -3069,7 +3149,9 @@ gtk_notebook_drag_motion (GtkWidget      *widget,
 
       if (widget_group != -1 &&
          source_widget_group != -1 &&
-         widget_group == source_widget_group)
+         widget_group == source_widget_group &&
+         !(widget == GTK_NOTEBOOK (source_widget)->cur_page->child || 
+           gtk_widget_is_ancestor (widget, GTK_NOTEBOOK (source_widget)->cur_page->child)))
        {
          gdk_drag_status (context, GDK_ACTION_MOVE, time);
          return TRUE;
@@ -3099,8 +3181,8 @@ gtk_notebook_drag_motion (GtkWidget      *widget,
           settings = gtk_widget_get_settings (widget);
 
           g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
-         priv->switch_tab_timer = g_timeout_add (timeout,
-                                                 (GSourceFunc) gtk_notebook_switch_tab_timeout,
+         priv->switch_tab_timer = gdk_threads_add_timeout (timeout,
+                                                 gtk_notebook_switch_tab_timeout,
                                                  widget);
        }
     }
@@ -3189,8 +3271,8 @@ do_detach_tab (GtkNotebook     *from,
   gtk_container_remove (GTK_CONTAINER (from), child);
 
   priv = GTK_NOTEBOOK_GET_PRIVATE (to);
-  priv->mouse_x = x;
-  priv->mouse_y = y;
+  priv->mouse_x = x + GTK_WIDGET (to)->allocation.x;
+  priv->mouse_y = y + GTK_WIDGET (to)->allocation.y;
 
   element = get_drop_position (to, tab_pack);
   page_num = g_list_position (to->children, element);
@@ -3518,11 +3600,24 @@ focus_tabs_move (GtkNotebook     *notebook,
 
   new_page = gtk_notebook_search_page (notebook, notebook->focus_tab,
                                       search_direction, TRUE);
+  if (!new_page)
+    {
+      gboolean wrap_around;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+                    "gtk-keynav-wrap-around", &wrap_around,
+                    NULL);
+
+      if (wrap_around)
+        new_page = gtk_notebook_search_page (notebook, NULL,
+                                             search_direction, TRUE);
+    }
+
   if (new_page)
     gtk_notebook_switch_focus_tab (notebook, new_page);
   else
-    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
-  
+    gtk_widget_error_bell (GTK_WIDGET (notebook));
+
   return TRUE;
 }
 
@@ -3947,68 +4042,6 @@ gtk_notebook_redraw_tabs (GtkNotebook *notebook)
   gdk_window_invalidate_rect (widget->window, &redraw_rect, TRUE);
 }
 
-static void
-gtk_notebook_redraw_tabs_union (GtkNotebook *notebook)
-{
-  GtkWidget *widget;
-  GtkNotebookPage *page;
-  GdkRectangle redraw_rect;
-  gint border;
-  gint tab_pos = get_effective_tab_pos (notebook);
-
-  widget = GTK_WIDGET (notebook);
-  border = GTK_CONTAINER (notebook)->border_width;
-
-  if (!GTK_WIDGET_MAPPED (notebook) || !notebook->first_tab)
-    return;
-
-  page = GTK_NOTEBOOK_PAGE (notebook->first_tab);
-
-  redraw_rect.x = border;
-  redraw_rect.y = border;
-
-  switch (tab_pos)
-    {
-    case GTK_POS_TOP:
-      redraw_rect.x = border;
-      redraw_rect.y = page->allocation.height + border;
-      break;
-    case GTK_POS_BOTTOM:
-      redraw_rect.x = border;
-      redraw_rect.y = widget->allocation.height - border -
-       page->allocation.height - widget->style->ythickness;
-      break;
-    case GTK_POS_RIGHT:
-      redraw_rect.x = widget->allocation.width - border -
-       page->allocation.width - widget->style->xthickness;
-      redraw_rect.y = border;
-      break;
-    case GTK_POS_LEFT:
-      redraw_rect.x = page->allocation.width + border;
-      redraw_rect.y = border;
-      break;
-    }
-
-  switch (tab_pos)
-    {
-    case GTK_POS_TOP:
-    case GTK_POS_BOTTOM:
-      redraw_rect.width = widget->allocation.width - 2 * border;
-      redraw_rect.height = widget->style->ythickness;
-      break;
-    case GTK_POS_RIGHT:
-    case GTK_POS_LEFT:
-      redraw_rect.width = widget->style->xthickness;
-      redraw_rect.height = widget->allocation.height - 2 * border;
-      break;
-    }
-
-  redraw_rect.x += widget->allocation.x;
-  redraw_rect.y += widget->allocation.y;
-
-  gdk_window_invalidate_rect (widget->window, &redraw_rect, TRUE);
-}
-
 static void
 gtk_notebook_redraw_arrows (GtkNotebook *notebook)
 {
@@ -4035,13 +4068,11 @@ gtk_notebook_redraw_arrows (GtkNotebook *notebook)
     }
 }
 
-static gint
+static gboolean
 gtk_notebook_timer (GtkNotebook *notebook)
 {
   gboolean retval = FALSE;
 
-  GDK_THREADS_ENTER ();
-
   if (notebook->timer)
     {
       gtk_notebook_do_arrow (notebook, notebook->click_child);
@@ -4055,7 +4086,7 @@ gtk_notebook_timer (GtkNotebook *notebook)
           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
 
          notebook->need_timer = FALSE;
-         notebook->timer = g_timeout_add (timeout * SCROLL_DELAY_FACTOR,
+         notebook->timer = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
                                           (GSourceFunc) gtk_notebook_timer,
                                           (gpointer) notebook);
        }
@@ -4063,8 +4094,6 @@ gtk_notebook_timer (GtkNotebook *notebook)
        retval = TRUE;
     }
 
-  GDK_THREADS_LEAVE ();
-
   return retval;
 }
 
@@ -4080,7 +4109,7 @@ gtk_notebook_set_scroll_timer (GtkNotebook *notebook)
 
       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
 
-      notebook->timer = g_timeout_add (timeout,
+      notebook->timer = gdk_threads_add_timeout (timeout,
                                       (GSourceFunc) gtk_notebook_timer,
                                       (gpointer) notebook);
       notebook->need_timer = TRUE;
@@ -4132,12 +4161,14 @@ static void
 gtk_notebook_real_remove (GtkNotebook *notebook,
                          GList       *list)
 {
+  GtkNotebookPrivate *priv;
   GtkNotebookPage *page;
   GList * next_list;
   gint need_resize = FALSE;
 
   gboolean destroying;
 
+  priv = GTK_NOTEBOOK_GET_PRIVATE (notebook);
   destroying = GTK_OBJECT_FLAGS (notebook) & GTK_IN_DESTRUCTION;
   
   next_list = gtk_notebook_search_page (notebook, list, STEP_PREV, TRUE);
@@ -4151,6 +4182,9 @@ gtk_notebook_real_remove (GtkNotebook *notebook,
        gtk_notebook_switch_page (notebook, GTK_NOTEBOOK_PAGE (next_list), -1);
     }
 
+  if (priv->detached_tab == list->data)
+    priv->detached_tab = NULL;
+
   if (list == notebook->first_tab)
     notebook->first_tab = next_list;
   if (list == notebook->focus_tab && !destroying)
@@ -4409,10 +4443,8 @@ gtk_notebook_paint (GtkWidget    *widget,
   if (!NOTEBOOK_IS_TAB_LABEL_PARENT (notebook, notebook->cur_page) ||
       !GTK_WIDGET_MAPPED (notebook->cur_page->tab_label))
     {
-      gtk_paint_box (widget->style, widget->window,
-                    GTK_STATE_NORMAL, GTK_SHADOW_OUT,
-                    area, widget, "notebook",
-                    x, y, width, height);
+      gap_x = 0;
+      gap_width = 0;
     }
   else
     {
@@ -4439,13 +4471,12 @@ gtk_notebook_paint (GtkWidget    *widget,
          step = STEP_PREV;
          break;
        }
-
-      gtk_paint_box_gap (widget->style, widget->window,
-                        GTK_STATE_NORMAL, GTK_SHADOW_OUT,
-                        area, widget, "notebook",
-                        x, y, width, height,
-                        tab_pos, gap_x, gap_width);
     }
+  gtk_paint_box_gap (widget->style, widget->window,
+                    GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+                    area, widget, "notebook",
+                    x, y, width, height,
+                    tab_pos, gap_x, gap_width);
 
   showarrow = FALSE;
   children = gtk_notebook_search_page (notebook, NULL, step, TRUE);
@@ -4624,7 +4655,7 @@ gtk_notebook_draw_arrow (GtkNotebook      *notebook,
           arrow = (ARROW_IS_LEFT (nbarrow) ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT);
           arrow_size = scroll_arrow_hlength;
         }
-      
+     
       gtk_paint_arrow (widget->style, widget->window, state_type, 
                       shadow_type, NULL, widget, "notebook",
                       arrow, TRUE, arrow_rect.x, arrow_rect.y, 
@@ -4650,7 +4681,6 @@ gtk_notebook_tab_space (GtkNotebook *notebook,
 {
   GtkNotebookPrivate *priv;
   GtkWidget *widget;
-  GtkNotebookPage *page;
   GList *children;
   gint tab_pos = get_effective_tab_pos (notebook);
   gint tab_overlap;
@@ -4677,6 +4707,8 @@ gtk_notebook_tab_space (GtkNotebook *notebook,
 
       while (children)
        {
+          GtkNotebookPage *page;
+
          page = children->data;
          children = children->next;
 
@@ -4692,6 +4724,8 @@ gtk_notebook_tab_space (GtkNotebook *notebook,
 
       while (children)
        {
+          GtkNotebookPage *page;
+
          page = children->data;
          children = children->next;
 
@@ -4715,7 +4749,6 @@ gtk_notebook_tab_space (GtkNotebook *notebook,
          if (*tab_space > *max - *min - tab_overlap)
            {
              *show_arrows = TRUE;
-             page = notebook->focus_tab->data;
 
              /* take arrows into account */
              *tab_space = widget->allocation.width - tab_overlap -
@@ -4751,7 +4784,6 @@ gtk_notebook_tab_space (GtkNotebook *notebook,
          if (*tab_space > *max - *min - tab_overlap)
            {
              *show_arrows = TRUE;
-             page = notebook->focus_tab->data;
 
              /* take arrows into account */
              *tab_space = widget->allocation.height -
@@ -4807,7 +4839,7 @@ gtk_notebook_calculate_shown_tabs (GtkNotebook *notebook,
                                  &(notebook->focus_tab),
                                  remaining_space, STEP_NEXT);
        }
-      
+
       if (*remaining_space <= 0)
        {
          /* show 1 tab */
@@ -4851,7 +4883,7 @@ gtk_notebook_calculate_shown_tabs (GtkNotebook *notebook,
                                    &(notebook->first_tab), remaining_space,
                                    STEP_PREV);
 
-         if (*remaining_space <= 0)
+         if (*remaining_space < 0)
            {
              notebook->first_tab =
                gtk_notebook_search_page (notebook, notebook->first_tab,
@@ -4883,16 +4915,21 @@ gtk_notebook_calculate_shown_tabs (GtkNotebook *notebook,
                {
                  *last_child = NULL;
                  children = NULL;
+
                  gtk_notebook_calc_tabs (notebook,
                                          gtk_notebook_search_page (notebook,
                                                                    notebook->first_tab,
                                                                    STEP_PREV,
                                                                    TRUE),
                                          &children, remaining_space, STEP_PREV);
-                 notebook->first_tab = gtk_notebook_search_page(notebook,
-                                                                children,
-                                                                STEP_NEXT,
-                                                                TRUE);
+
+                 if (*remaining_space == 0)
+                   notebook->first_tab = children;
+                 else
+                   notebook->first_tab = gtk_notebook_search_page(notebook,
+                                                                  children,
+                                                                  STEP_NEXT,
+                                                                  TRUE);
                }
            }
        }
@@ -4943,8 +4980,9 @@ gtk_notebook_calculate_shown_tabs (GtkNotebook *notebook,
       *n = 0;
 
       *remaining_space = max - min - tab_overlap - tab_space;
-      notebook->first_tab = children = gtk_notebook_search_page (notebook, NULL,
-                                                                STEP_NEXT, TRUE);
+      children = notebook->children;
+      notebook->first_tab = gtk_notebook_search_page (notebook, NULL,
+                                                     STEP_NEXT, TRUE);
       while (children)
        {
          page = children->data;
@@ -4998,9 +5036,8 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
                                        GList        *last_child,
                                        gboolean      showarrow,
                                        gint          direction,
-                                       gint          n,
                                        gint         *remaining_space,
-                                       gint         *i,
+                                       gint         *expanded_tabs,
                                        gint          min,
                                        gint          max)
 {
@@ -5009,9 +5046,9 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
   GtkNotebookPrivate *priv;
   GtkNotebookPage *page;
   gboolean allocate_at_bottom;
-  gint tab_overlap, tab_pos, delta;
-  gint new_fill, old_fill;
+  gint tab_overlap, tab_pos, tab_extra_space;
   gint left_x, right_x, top_y, bottom_y, anchor;
+  gint xthickness, ythickness;
   gboolean gap_left, packing_changed;
   GtkAllocation child_allocation = { 0, };
   gboolean allocation_changed = FALSE;
@@ -5022,11 +5059,14 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
   gtk_widget_style_get (widget, "tab-overlap", &tab_overlap, NULL);
   tab_pos = get_effective_tab_pos (notebook);
   allocate_at_bottom = get_allocate_at_bottom (widget, direction);
-  anchor = old_fill = 0;
+  anchor = 0;
 
   child_allocation.x = widget->allocation.x + container->border_width;
   child_allocation.y = widget->allocation.y + container->border_width;
 
+  xthickness = widget->style->xthickness;
+  ythickness = widget->style->ythickness;
+
   switch (tab_pos)
     {
     case GTK_POS_BOTTOM:
@@ -5050,9 +5090,9 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
       break;
     }
 
-  left_x   = CLAMP (widget->allocation.x + priv->mouse_x - notebook->cur_page->allocation.width / 2,
+  left_x   = CLAMP (priv->mouse_x - priv->drag_offset_x,
                    min, max - notebook->cur_page->allocation.width);
-  top_y    = CLAMP (widget->allocation.y + priv->mouse_y - notebook->cur_page->allocation.height / 2,
+  top_y    = CLAMP (priv->mouse_y - priv->drag_offset_y,
                    min, max - notebook->cur_page->allocation.height);
   right_x  = left_x + notebook->cur_page->allocation.width;
   bottom_y = top_y + notebook->cur_page->allocation.height;
@@ -5083,19 +5123,19 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
       if (!NOTEBOOK_IS_TAB_LABEL_PARENT (notebook, page))
        continue;
 
-      delta = 0;
-      if (n && (showarrow || page->expand || notebook->homogeneous))
+      tab_extra_space = 0;
+      if (*expanded_tabs && (showarrow || page->expand || notebook->homogeneous))
        {
-         new_fill = (*remaining_space * (*i)++) / n;
-         delta = new_fill - old_fill;
-         old_fill = new_fill;
+         tab_extra_space = *remaining_space / *expanded_tabs;
+         *remaining_space -= tab_extra_space;
+         (*expanded_tabs)--;
        }
 
       switch (tab_pos)
        {
        case GTK_POS_TOP:
        case GTK_POS_BOTTOM:
-         child_allocation.width = page->requisition.width + tab_overlap + delta;
+         child_allocation.width = page->requisition.width + tab_overlap + tab_extra_space;
 
          /* make sure that the reordered tab doesn't go past the last position */
          if (priv->operation == DRAG_OPERATION_REORDER &&
@@ -5152,7 +5192,7 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
          break;
        case GTK_POS_LEFT:
        case GTK_POS_RIGHT:
-         child_allocation.height = page->requisition.height + tab_overlap + delta;
+         child_allocation.height = page->requisition.height + tab_overlap + tab_extra_space;
 
          /* make sure that the reordered tab doesn't go past the last position */
          if (priv->operation == DRAG_OPERATION_REORDER &&
@@ -5206,13 +5246,34 @@ gtk_notebook_calculate_tabs_allocation (GtkNotebook  *notebook,
 
       page->allocation = child_allocation;
 
-      if (priv->operation == DRAG_OPERATION_REORDER &&
-         page == notebook->cur_page)
+      if (page == notebook->cur_page)
        {
-         /* needs to be allocated at 0,0
-          * to be shown in the drag window */
-         page->allocation.x = 0;
-         page->allocation.y = 0;
+         if (priv->operation == DRAG_OPERATION_REORDER ||
+             priv->operation == DRAG_OPERATION_DETACH)
+           {
+             /* needs to be allocated at 0,0
+              * to be shown in the drag window */
+             page->allocation.x = 0;
+             page->allocation.y = 0;
+           }
+       }
+      else
+       {
+         switch (tab_pos)
+           {
+           case GTK_POS_TOP:
+             page->allocation.y += ythickness;
+             /* fall through */
+           case GTK_POS_BOTTOM:
+             page->allocation.height = MAX (1, page->allocation.height - ythickness);
+             break;
+           case GTK_POS_LEFT:
+             page->allocation.x += xthickness;
+             /* fall through */
+           case GTK_POS_RIGHT:
+             page->allocation.width = MAX (1, page->allocation.width - xthickness);
+             break;
+           }
        }
 
       /* calculate whether to leave a gap based on reorder operation or not */
@@ -5316,32 +5377,31 @@ gtk_notebook_pages_allocate (GtkNotebook *notebook)
   GList *last_child = NULL;
   gboolean showarrow = FALSE;
   gint tab_space, min, max, remaining_space;
-  gint i, n, operation;
-  gboolean changed;
+  gint expanded_tabs, operation;
 
   if (!notebook->show_tabs || !notebook->children || !notebook->cur_page)
     return;
 
   min = max = tab_space = remaining_space = 0;
-  i = n = 1;
+  expanded_tabs = 1;
 
   gtk_notebook_tab_space (notebook, &showarrow,
                          &min, &max, &tab_space);
 
   gtk_notebook_calculate_shown_tabs (notebook, showarrow,
                                     min, max, tab_space, &last_child,
-                                    &n, &remaining_space);
+                                    &expanded_tabs, &remaining_space);
 
   children = notebook->first_tab;
-  changed = gtk_notebook_calculate_tabs_allocation (notebook, &children, last_child,
-                                                   showarrow, STEP_NEXT,
-                                                   n, &remaining_space, &i, min, max);
+  gtk_notebook_calculate_tabs_allocation (notebook, &children, last_child,
+                                         showarrow, STEP_NEXT,
+                                         &remaining_space, &expanded_tabs, min, max);
   if (children && children != last_child)
     {
       children = notebook->children;
-      changed |= gtk_notebook_calculate_tabs_allocation (notebook, &children, last_child,
-                                                        showarrow, STEP_PREV,
-                                                        n, &remaining_space, &i, min, max);
+      gtk_notebook_calculate_tabs_allocation (notebook, &children, last_child,
+                                             showarrow, STEP_PREV,
+                                             &remaining_space, &expanded_tabs, min, max);
     }
 
   children = notebook->children;
@@ -5357,10 +5417,7 @@ gtk_notebook_pages_allocate (GtkNotebook *notebook)
   if (!notebook->first_tab)
     notebook->first_tab = notebook->children;
 
-  if (changed || operation != DRAG_OPERATION_REORDER)
-    gtk_notebook_redraw_tabs (notebook);
-  else if (!changed)
-    gtk_notebook_redraw_tabs_union (notebook);
+  gtk_notebook_redraw_tabs (notebook);
 }
 
 static void
@@ -5456,8 +5513,10 @@ gtk_notebook_calc_tabs (GtkNotebook  *notebook,
   GtkNotebookPage *page = NULL;
   GList *children;
   GList *last_list = NULL;
+  GList *last_calculated_child = NULL;
   gboolean pack;
   gint tab_pos = get_effective_tab_pos (notebook);
+  guint real_direction;
 
   if (!start)
     return;
@@ -5465,7 +5524,9 @@ gtk_notebook_calc_tabs (GtkNotebook  *notebook,
   children = start;
   pack = GTK_NOTEBOOK_PAGE (start)->pack;
   if (pack == GTK_PACK_END)
-    direction = (direction == STEP_PREV) ? STEP_NEXT : STEP_PREV;
+    real_direction = (direction == STEP_PREV) ? STEP_NEXT : STEP_PREV;
+  else
+    real_direction = direction;
 
   while (1)
     {
@@ -5488,14 +5549,20 @@ gtk_notebook_calc_tabs (GtkNotebook  *notebook,
                            {
                              *tab_space = - (*tab_space +
                                              page->requisition.width);
+
+                             if (*tab_space == 0 && direction == STEP_PREV)
+                               children = last_calculated_child;
+
                              *end = children;
                            }
                          return;
                        }
+
+                     last_calculated_child = children;
                    }
                  last_list = children;
                }
-             if (direction == STEP_NEXT)
+             if (real_direction == STEP_NEXT)
                children = children->next;
              else
                children = children->prev;
@@ -5518,24 +5585,30 @@ gtk_notebook_calc_tabs (GtkNotebook  *notebook,
                            {
                              *tab_space = - (*tab_space +
                                              page->requisition.height);
+
+                             if (*tab_space == 0 && direction == STEP_PREV)
+                               children = last_calculated_child;
+
                              *end = children;
                            }
                          return;
                        }
+
+                     last_calculated_child = children;
                    }
                  last_list = children;
                }
-             if (direction == STEP_NEXT)
+             if (real_direction == STEP_NEXT)
                children = children->next;
              else
                children = children->prev;
            }
          break;
        }
-      if (direction == STEP_PREV)
+      if (real_direction == STEP_PREV)
        return;
       pack = (pack == GTK_PACK_END) ? GTK_PACK_START : GTK_PACK_END;
-      direction = STEP_PREV;
+      real_direction = STEP_PREV;
       children = last_list;
     }
 }
@@ -6239,8 +6312,13 @@ gtk_notebook_page_num (GtkNotebook      *notebook,
  *            than the number of pages in the notebook, nothing
  *            will be done.
  *                
- * Switches to the page number @page_num.
- **/
+ * Switches to the page number @page_num. 
+ *
+ * Note that due to historical reasons, GtkNotebook refuses
+ * to switch to a page unless the child widget is visible. 
+ * Therefore, it is recommended to show child widgets before
+ * adding them to a notebook. 
+ */
 void
 gtk_notebook_set_current_page (GtkNotebook *notebook,
                               gint         page_num)
@@ -7127,8 +7205,9 @@ gtk_notebook_reorder_child (GtkNotebook *notebook,
 
 /**
  * gtk_notebook_set_window_creation_hook:
- * @func: the #GtkNotebookWindowCreationFunc, or NULL
- * @data: user data for @func.
+ * @func: the #GtkNotebookWindowCreationFunc, or %NULL
+ * @data: user data for @func
+ * @destroy: Destroy notifier for @data, or %NULL
  *
  * Installs a global function used to create a window
  * when a detached tab is dropped in an empty area.
@@ -7137,10 +7216,15 @@ gtk_notebook_reorder_child (GtkNotebook *notebook,
  **/
 void
 gtk_notebook_set_window_creation_hook (GtkNotebookWindowCreationFunc  func,
-                                      gpointer                       data)
+                                      gpointer                       data,
+                                       GDestroyNotify                 destroy)
 {
+  if (window_creation_hook_destroy)
+    window_creation_hook_destroy (window_creation_hook_data);
+
   window_creation_hook = func;
   window_creation_hook_data = data;
+  window_creation_hook_destroy = destroy;
 }
 
 /**