Logo Search packages:      
Sourcecode: galeon version File versions

gul-notebook.c

/*
 *  Copyright (C) 2002 Christophe Fergeau
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * This GTK2 widget extends the standard GtkNotebook with tab dragging support.
 * Even if this is called GulNotebook, please try not to add galeon specific
 * code in there if possible, so other apps like gedit or profterm can use 
 * it easily if they want.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gul-notebook.h"
#include "gul-gui.h"
#include "gul-string.h"
#include "galeon-marshal.h"

#include <gtk/gtkalignment.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtktooltips.h>
#include <gtk/gtkstock.h>
#include <gtk/gtkimage.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkeventbox.h>

#include <glib-object.h>
#include <glib/gi18n.h>

#define AFTER_ALL_TABS          -1
#define NOT_IN_APP_WINDOWS      -2

#define DEFAULT_TAB_WIDTH_CHARS 15
#define MENU_ITEM_MAX_LENGTH    40

/* spacing between tab border and favicon/label, and between label and close
 * button */
#define SPACING              3

struct GulNotebookPrivate 
{
      GList *focused_pages;
      GList *opened_tabs;
      GtkPolicyType policy;
      gboolean automatic_tab_switch;
      gint tab_width_chars;
      
      /* Used during tab drag'n'drop */
      gulong motion_notify_handler_id;
      gint x_start, y_start;
      gboolean drag_in_progress;
};

/* GObject boilerplate code */
static void gul_notebook_init         (GulNotebook *notebook);
static void gul_notebook_class_init   (GulNotebookClass *klass);
static void gul_notebook_finalize     (GObject *object);

/* Local variables */
static GdkCursor *cursor = NULL;
static GList *notebooks  = NULL;
static GtkTooltips *_gul_notebook_tooltips = NULL;


/* Local functions */
static void drag_start (GulNotebook *notebook);
static void drag_stop  (GulNotebook *notebook);

static gboolean motion_notify_cb (GulNotebook *notebook, 
                          GdkEventMotion *event, 
                          gpointer data);

/* Signals */
enum
{
      TAB_ADDED,
      TAB_REMOVED,
      TABS_REORDERED,
      TAB_DETACHED,
      TAB_DELETE,
      LAST_SIGNAL
};

static guint gul_notebook_signals[LAST_SIGNAL] = { 0 };
static GObjectClass *parent_class = NULL;

GType 
gul_notebook_get_type (void)
{
        static GType gul_notebook_type = 0;

        if (gul_notebook_type == 0)
        {
                static const GTypeInfo our_info =
                  {
                        sizeof (GulNotebookClass),
                        NULL, /* base_init */
                        NULL, /* base_finalize */
                        (GClassInitFunc) gul_notebook_class_init,
                        NULL,
                        NULL, /* class_data */
                        sizeof (GulNotebook),
                        0, /* n_preallocs */
                        (GInstanceInitFunc) gul_notebook_init
                  };
            
                gul_notebook_type = g_type_register_static (GTK_TYPE_NOTEBOOK,
                                               "GulNotebook",
                                               &our_info, 0);
        }

        return gul_notebook_type;
}

static void
gul_notebook_class_init (GulNotebookClass *klass)
{
      GObjectClass *object_class = G_OBJECT_CLASS (klass);
      parent_class = g_type_class_peek_parent (klass);

      object_class->finalize = gul_notebook_finalize;

      /* init signals */
      gul_notebook_signals[TAB_ADDED] =
            g_signal_new ("tab_added",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_FIRST,
                        G_STRUCT_OFFSET (GulNotebookClass, tab_added),
                        NULL, NULL,
                        galeon_marshal_VOID__OBJECT,
                        G_TYPE_NONE,
                        1,
                        GTK_TYPE_WIDGET);
      gul_notebook_signals[TAB_REMOVED] =
            g_signal_new ("tab_removed",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_FIRST,
                        G_STRUCT_OFFSET (GulNotebookClass, tab_removed),
                        NULL, NULL,
                        galeon_marshal_VOID__OBJECT,
                        G_TYPE_NONE,
                        1,
                        GTK_TYPE_WIDGET);
      gul_notebook_signals[TAB_DETACHED] =
            g_signal_new ("tab_detached",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (GulNotebookClass, 
                                     tab_detached),
                        NULL, NULL,
                        galeon_marshal_VOID__INT_INT_INT,
                        G_TYPE_NONE,
                        3, 
                        G_TYPE_INT,
                        G_TYPE_INT,
                        G_TYPE_INT);
      gul_notebook_signals[TABS_REORDERED] =
            g_signal_new ("tabs_reordered",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_FIRST,
                        G_STRUCT_OFFSET (GulNotebookClass, tabs_reordered),
                        NULL, NULL,
                        galeon_marshal_VOID__VOID,
                        G_TYPE_NONE,
                        0);

      gul_notebook_signals[TAB_DELETE] =
            g_signal_new ("tab_delete",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (GulNotebookClass, tab_delete),
                        NULL, NULL,
                        galeon_marshal_BOOLEAN__OBJECT,
                        G_TYPE_BOOLEAN,
                        1,
                        GTK_TYPE_WIDGET);
}

static gboolean 
is_in_notebook_window (GulNotebook *notebook, 
                   gint abs_x, gint abs_y)
{
      gint x, y;
      gint rel_x, rel_y;
      gint width, height;
      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET(notebook));
      GdkWindow *window = GTK_WIDGET(toplevel)->window;
      
      gdk_window_get_origin (window, &x, &y);
      rel_x = abs_x - x;
      rel_y = abs_y - y;

      x = GTK_WIDGET(notebook)->allocation.x;
      y = GTK_WIDGET(notebook)->allocation.y;
      height = GTK_WIDGET(notebook)->allocation.height;
      width  = GTK_WIDGET(notebook)->allocation.width;
      return ((rel_x>=x) && (rel_y>=y) && (rel_x<=x+width) && (rel_y<=y+height));
}

static GulNotebook *
find_notebook_at_pointer (gint abs_x, gint abs_y)
{
      GList *l;
      gint x, y;
      GdkWindow *win_at_pointer = gdk_window_at_pointer (&x, &y);
      GdkWindow *toplevel_at_pointer = NULL;

      if (win_at_pointer == NULL) 
      {
            /* We are outside all windows containing a notebook */
            return NULL;
      }

      /* Get the toplevel window for where the pointer is currently */
      toplevel_at_pointer = gdk_window_get_toplevel (win_at_pointer);

      for (l = notebooks; l != NULL; l = l->next) 
      {
            GulNotebook *nb = GUL_NOTEBOOK (l->data);
            GdkWindow *win = GTK_WIDGET (nb)->window;

            win = gdk_window_get_toplevel (win);
            if (win == toplevel_at_pointer
                && is_in_notebook_window (nb, abs_x, abs_y)) 
            {
                  return nb;
            }
      }
      return NULL;
}


static gint
find_tab_num_at_pos (GulNotebook *notebook, gint abs_x, gint abs_y)
{
      GtkPositionType tab_pos;
      int page_num = 0;
      GtkNotebook *nb = GTK_NOTEBOOK (notebook);
      GtkWidget *page;
      
      tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));

      if (GTK_NOTEBOOK (notebook)->first_tab == NULL) 
      {
            return AFTER_ALL_TABS;
      }
      
      g_assert (is_in_notebook_window(notebook, abs_x, abs_y));
      
      while ((page = gtk_notebook_get_nth_page (nb, page_num)))  
      {
            GtkWidget *tab;
            gint max_x, max_y;
            gint x_root, y_root;
            
            tab = gtk_notebook_get_tab_label (nb, page);
            g_return_val_if_fail (tab != NULL, -1);
            
            if (!GTK_WIDGET_MAPPED (GTK_WIDGET (tab))) 
            {
                  page_num++;
                  continue;
            }

            gdk_window_get_origin (GDK_WINDOW (tab->window), 
                               &x_root, &y_root);

            max_x = x_root + tab->allocation.x + tab->allocation.width;
            max_y = y_root + tab->allocation.y + tab->allocation.height;

            if (((tab_pos == GTK_POS_TOP) 
                 || (tab_pos == GTK_POS_BOTTOM)) 
                &&(abs_x<=max_x)) 
            {
                  return page_num;
            } 
            else if (((tab_pos == GTK_POS_LEFT) 
                    || (tab_pos == GTK_POS_RIGHT))
                   && (abs_y<=max_y)) 
            {
                  return page_num;
            }                 

            page_num++;
      }
      return AFTER_ALL_TABS;
}


static gint find_notebook_and_tab_at_pos (gint abs_x, gint abs_y, 
                                GulNotebook **notebook, 
                                gint *page_num)
{
      *notebook = find_notebook_at_pointer (abs_x, abs_y);
      if (*notebook == NULL) 
      {
            return NOT_IN_APP_WINDOWS;
      }
      *page_num = find_tab_num_at_pos (*notebook, abs_x, abs_y);
      
      if (*page_num < 0) 
      {
            return *page_num;
      }
      else 
      {
            return 0;
      }
}

static GtkWidget *
tab_get_label (GulNotebook *nb, GtkWidget *child)
{
      GtkWidget *hbox, *label;

      hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb),
                                 child);
      label = g_object_get_data (G_OBJECT (hbox), "label");

      return label;
}


static GtkWidget *
tab_get_icon (GulNotebook *nb, GtkWidget *child)
{
      GtkWidget *hbox, *icon;

      hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb),
                                 child);
      icon = g_object_get_data (G_OBJECT (hbox), "icon");

      return icon;
}

void
gul_notebook_move_page (GulNotebook *src, GulNotebook *dest, 
                  GtkWidget *src_page,  gint dest_page)
{
      GtkWidget *tab_label, *icon;
      const gchar * text;

      if (dest == NULL || src == dest)
      {
            gtk_notebook_reorder_child (GTK_NOTEBOOK (src), src_page, dest_page);
            
            if (src->priv->drag_in_progress == FALSE)
            {
                  g_signal_emit (G_OBJECT (src), 
                               gul_notebook_signals[TABS_REORDERED], 0);
            }
            return;
      }

      tab_label = tab_get_label (src, src_page);
      icon      = tab_get_icon  (src, src_page);

      /* We don't want gtk to destroy tab and src_page behind our back */
      g_object_ref (G_OBJECT (src_page));
      g_object_ref (G_OBJECT (tab_label));
      if (icon) g_object_ref (G_OBJECT (icon));

      gul_notebook_remove_page (GUL_NOTEBOOK (src), src_page);
      gul_notebook_insert_page (GUL_NOTEBOOK (dest), src_page,
                          dest_page, TRUE);
      /* Restore the text  */
      text = gtk_label_get_text( GTK_LABEL(tab_label) );
      gul_notebook_set_page_title( dest, src_page, text );
      if (icon)
      {
            gul_notebook_set_page_icon (dest, src_page, icon);
            g_object_unref (G_OBJECT (icon));
      }
      g_object_unref (G_OBJECT (src_page));
      g_object_unref (G_OBJECT (tab_label));
}



static void
move_tab_to_another_notebook(GulNotebook *src, 
                       GulNotebook *dest, gint dest_page)
{
      GtkWidget *child;
      gint cur_page;

      /* This is getting tricky, the tab was dragged in a notebook
       * in another window of the same app, we move the tab
       * to that new notebook, and let this notebook handle the
       * drag
      */
      g_assert (dest != NULL);
      g_assert (dest != src);

      /* Move the widgets (tab label and tab content) to the new 
       * notebook
       */
      cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src));
      child    = gtk_notebook_get_nth_page (GTK_NOTEBOOK (src), cur_page);
      gul_notebook_move_page (src, dest, child, dest_page);

      /* "Give" drag handling to the new notebook */
      drag_start (dest);
      drag_stop (src);        
      gtk_grab_remove (GTK_WIDGET (src));
      
      dest->priv->motion_notify_handler_id = 
            g_signal_connect (G_OBJECT (dest), 
                          "motion-notify-event",
                          G_CALLBACK (motion_notify_cb),
                          NULL);
}


static void 
move_tab (GulNotebook *notebook, gint dest_page_num)
{
      gint cur_page_num;

      cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));

      if (dest_page_num != cur_page_num)
      {
            GtkWidget *cur_page;
            cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook),
                                          cur_page_num);
            gtk_notebook_reorder_child (GTK_NOTEBOOK (notebook), cur_page,
                                  dest_page_num);

            /* Reset the list of newly opened tabs when moving tabs. */
            g_list_free (notebook->priv->opened_tabs);
            notebook->priv->opened_tabs = NULL;
      }
}

static void 
drag_start (GulNotebook *notebook)
{
      notebook->priv->drag_in_progress = TRUE;

      /* get a new cursor, if necessary */
      if (!cursor) cursor = gdk_cursor_new (GDK_FLEUR);
      
      /* grab the pointer */
      gtk_grab_add (GTK_WIDGET (notebook));
      if (!gdk_pointer_is_grabbed ()) {
            gdk_pointer_grab (GDK_WINDOW(GTK_WIDGET (notebook)->window), 
                          FALSE,
                          GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                          NULL, cursor, GDK_CURRENT_TIME);
      }
}

static void 
drag_stop (GulNotebook *notebook)
{
      if (notebook->priv->drag_in_progress)
      {
            g_signal_emit (G_OBJECT (notebook), 
                         gul_notebook_signals[TABS_REORDERED], 0);
      }

      notebook->priv->drag_in_progress = FALSE;

      if (notebook->priv->motion_notify_handler_id != 0) 
      {
            g_signal_handler_disconnect (G_OBJECT (notebook), 
                                   notebook->priv->motion_notify_handler_id);
            notebook->priv->motion_notify_handler_id = 0;
      }       
}

/* Callbacks */
static gboolean
button_release_cb (GulNotebook *notebook, GdkEventButton *event,
               gpointer data)
{
      if (notebook->priv->drag_in_progress) 
      {
            gint cur_page_num;
            GtkWidget *cur_page; 

            cur_page_num = 
                  gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
            cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), 
                                          cur_page_num);

            if (!is_in_notebook_window (notebook, event->x_root, event->y_root)) 
            {
                  /* Tab was detached */
                  g_signal_emit (G_OBJECT(notebook), 
                               gul_notebook_signals[TAB_DETACHED], 0,
                               cur_page_num, (gint)event->x_root, 
                               (gint)event->y_root);
            }

            /* ungrab the pointer if it's grabbed */
            if (gdk_pointer_is_grabbed ())
            {
                  gdk_pointer_ungrab (GDK_CURRENT_TIME);
                  gtk_grab_remove (GTK_WIDGET (notebook));
            }
      }
      /* This must be called even if a drag isn't happening */
      drag_stop (notebook);
      return FALSE;
}


static gboolean 
motion_notify_cb (GulNotebook *notebook, GdkEventMotion *event, 
              gpointer data)
{
      GulNotebook *dest;
      gint page_num;
      gint result;

      /* If the notebook only has one tab, we don't want to do
       * anything since galeon can't handle empty notebooks
       */
      if (g_list_length (GTK_NOTEBOOK (notebook)->children) <= 1) {
            return FALSE;
      }
      
      if (notebook->priv->drag_in_progress == FALSE)
      {
            if (gtk_drag_check_threshold (GTK_WIDGET (notebook),
                                    notebook->priv->x_start, 
                                    notebook->priv->y_start, 
                                    event->x_root, event->y_root))
            {
                  drag_start (notebook);
            }
            else
            {
                  return FALSE;
            }
      }

      result = find_notebook_and_tab_at_pos ((gint)event->x_root, 
                                     (gint)event->y_root,
                                     &dest, &page_num);

      if (result != NOT_IN_APP_WINDOWS) 
      {
            if (dest != notebook) 
            {
                  move_tab_to_another_notebook (notebook, dest, 
                                          page_num);
            } 
            else 
            {
                  g_assert (page_num >= -1);
                  move_tab (notebook, page_num);
            }
      }

      return FALSE;
}

static gboolean 
button_press_cb (GulNotebook *notebook, 
             GdkEventButton *event,
             gpointer data)
{
      gint tab_clicked = find_tab_num_at_pos (notebook, 
                                    event->x_root, 
                                    event->y_root);
      
      if (notebook->priv->drag_in_progress)
      {
            return TRUE;
      }

      if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS)
          && (tab_clicked != -1))
      {
            notebook->priv->x_start = event->x_root;
            notebook->priv->y_start = event->y_root;
            notebook->priv->motion_notify_handler_id = 
                  g_signal_connect (G_OBJECT (notebook), 
                                "motion-notify-event",
                                G_CALLBACK (motion_notify_cb), NULL);
      }

      /* Switch tabs on right click. This means that when the popup
       * menu is shown, it will be clear that it acts on the tab under
       * the mouse. popup menu is attached by the GulNotebook user if
       * the default is not desired.
       */
      else if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS))
      {
            if (tab_clicked != -1)
                  gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook),
                                          tab_clicked);
            else
                  return TRUE;
      }

      return FALSE;
}

GtkWidget *
gul_notebook_new (void)
{
      return GTK_WIDGET (g_object_new (GUL_TYPE_NOTEBOOK, NULL));
}

static void
gul_notebook_switch_page_cb (GtkNotebook *notebook,
                             GtkNotebookPage *page,
                             guint page_num,
                             gpointer data)
{
      GulNotebook *nb = GUL_NOTEBOOK (notebook);
      GtkWidget *child;
      
      child = gtk_notebook_get_nth_page (notebook, page_num);

      if (nb->priv->automatic_tab_switch)
      {
            /* Remove the old page, we dont want to grow unnecessarily
             * the list */
            if (nb->priv->focused_pages)
            {
                  nb->priv->focused_pages = 
                        g_list_remove (nb->priv->focused_pages, child);
            }
      }
      else
      {
            g_list_free (nb->priv->focused_pages);
            nb->priv->focused_pages = NULL;
      }

      nb->priv->focused_pages = g_list_append (nb->priv->focused_pages,
                                     child);

      /* Reset the list of newly opened tabs when switching tabs. */
      g_list_free (nb->priv->opened_tabs);
      nb->priv->opened_tabs = NULL;
}

static void
gul_notebook_init (GulNotebook *notebook)
{
      if (!_gul_notebook_tooltips)
      {
            _gul_notebook_tooltips = gtk_tooltips_new ();
            g_object_add_weak_pointer (G_OBJECT (_gul_notebook_tooltips),
                                 (gpointer)&_gul_notebook_tooltips);
      }
      g_object_ref (_gul_notebook_tooltips);
      gtk_object_sink (GTK_OBJECT (_gul_notebook_tooltips));

      notebook->priv = g_new (GulNotebookPrivate, 1);

      notebook->priv->drag_in_progress = FALSE;
      notebook->priv->motion_notify_handler_id = 0;
      notebook->priv->focused_pages = NULL;
      notebook->priv->automatic_tab_switch = FALSE;
      notebook->priv->opened_tabs = NULL;
      notebook->priv->policy = GTK_POLICY_AUTOMATIC;
      notebook->priv->tab_width_chars = DEFAULT_TAB_WIDTH_CHARS;
      
      notebooks = g_list_append (notebooks, notebook);

      g_signal_connect (notebook, "button-press-event", 
                    (GCallback)button_press_cb, NULL);
      g_signal_connect (notebook, "button-release-event", 
                    (GCallback)button_release_cb, NULL);
      gtk_widget_add_events (GTK_WIDGET (notebook), GDK_BUTTON1_MOTION_MASK);

      g_signal_connect_after (G_OBJECT (notebook), "switch_page",
                                G_CALLBACK (gul_notebook_switch_page_cb),
                                NULL);
}

static void 
gul_notebook_finalize (GObject *object)
{
      GulNotebook *notebook = GUL_NOTEBOOK (object);

      notebooks = g_list_remove (notebooks, notebook);

      g_list_free (notebook->priv->focused_pages);
      g_list_free (notebook->priv->opened_tabs);

      g_free (notebook->priv);

      g_object_unref (_gul_notebook_tooltips);

      G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
tab_close_button_clicked_cb (GtkWidget *widget,
                       GtkWidget *child)
{
      GulNotebook *notebook;
      gboolean inhibited = FALSE;

      notebook = GUL_NOTEBOOK (gtk_widget_get_parent (child));

      g_signal_emit (G_OBJECT (notebook), gul_notebook_signals[TAB_DELETE],
                   0, child, &inhibited);

      if (inhibited == FALSE)
      {
            gul_notebook_remove_page (notebook, child);
      }
}

static void
tab_label_style_set (GtkWidget *label, GtkStyle *previous_style, GtkWidget *hbox)
{
      GulNotebook      *notebook;
      PangoFontMetrics *metrics;
      PangoContext       *context;
      gint            char_width;
      gint              n_pixels;

      notebook = GUL_NOTEBOOK (gtk_widget_get_ancestor (hbox, GUL_TYPE_NOTEBOOK));

      context = gtk_widget_get_pango_context (label);
      metrics = pango_context_get_metrics (context,
                                       label->style->font_desc,
                                   pango_context_get_language (context));

      char_width = pango_font_metrics_get_approximate_char_width (metrics);
      pango_font_metrics_unref (metrics);

      n_pixels = notebook->priv->tab_width_chars * PANGO_PIXELS(char_width);

      /* We need to set the width of the widget that contains not only the
       * label, but also the favicon as otherwise tabs with icon would be
       * wider and we want to avoid that.  Consequently we need to take the
       * icon into account so that we really have the space for 'n'
       * characters when the icon is shown (and a little more when not.)
       */

      n_pixels += 16;
      n_pixels += SPACING;

      hbox = g_object_get_data (G_OBJECT (hbox), "label-hbox");
      gtk_widget_set_size_request (hbox, n_pixels, -1);
}

static GtkWidget *
tab_build_label (GulNotebook *nb, GtkWidget *child)
{
      GtkWidget *label, *hbox, *close_button, *image, *label_hbox;
      GtkRequisition size;
      GtkRcStyle *rcstyle;
      GtkWidget *window;
      GtkWidget *label_ebox;
      GtkWidget *alignment;

      window = gtk_widget_get_toplevel (GTK_WIDGET (nb));
      
      /* Add some space between the label and the close button, this
       * should be the same as the left-padding for the label */
      hbox = gtk_hbox_new (FALSE, SPACING);

      /* setup close button, zero out {x,y}thickness to get smallest possible
       * size */
      close_button = gtk_button_new ();
      gtk_button_set_focus_on_click (GTK_BUTTON (close_button), FALSE);
      gtk_button_set_relief (GTK_BUTTON (close_button), 
                         GTK_RELIEF_NONE);
      rcstyle = gtk_rc_style_new ();
      rcstyle->xthickness = rcstyle->ythickness = 0;
      gtk_widget_modify_style (close_button, rcstyle);
      gtk_rc_style_unref (rcstyle),

      image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
                                GTK_ICON_SIZE_MENU);
      gtk_widget_size_request (image, &size);
      gtk_widget_set_size_request (close_button, size.width, size.height);
      gtk_container_add (GTK_CONTAINER (close_button), image);

      gtk_tooltips_set_tip (_gul_notebook_tooltips, close_button,
                        _("Close this tab"), NULL);

      /* setup label, need eventbox for tooltips and left-padding to get
       * comfortable space between the label and icon/tab border */
      label_ebox = gtk_event_box_new ();
      gtk_event_box_set_visible_window (GTK_EVENT_BOX (label_ebox), FALSE);
      label_hbox = gtk_hbox_new (FALSE, 0);
        label = gtk_label_new (_("Untitled"));
      gtk_misc_set_alignment (GTK_MISC (label), 0.00, 0.5);

      alignment = g_object_new (GTK_TYPE_ALIGNMENT,
                          "xalign",       0.0,
                          "left-padding", SPACING,
                          NULL);
      gtk_container_add (GTK_CONTAINER (alignment), label);
      gtk_widget_show (alignment);
      gtk_box_pack_end (GTK_BOX (label_hbox), alignment, TRUE, TRUE, 0);

      gtk_box_pack_start (GTK_BOX (hbox), label_ebox, TRUE, TRUE, 0);
      gtk_container_add (GTK_CONTAINER (label_ebox), label_hbox);

      /* setup button */
      gtk_box_pack_start (GTK_BOX (hbox), close_button,
                            FALSE, FALSE, 0);

      g_signal_connect (G_OBJECT (close_button), "clicked",
                          G_CALLBACK (tab_close_button_clicked_cb),
                          child);

      gtk_widget_show (hbox);
      gtk_widget_show (label_ebox);
      gtk_widget_show (label_hbox);
      gtk_widget_show (label);
      gtk_widget_show (image);
      gtk_widget_show (close_button);

      g_object_set_data (G_OBJECT (hbox), "label", label);
      g_object_set_data (G_OBJECT (hbox), "label-ebox", label_ebox);
      g_object_set_data (G_OBJECT (hbox), "label-hbox", label_hbox);
      g_object_set_data (G_OBJECT (hbox), "icon", 0);

      g_signal_connect (label, "style-set", G_CALLBACK (tab_label_style_set), hbox);

      return hbox;
}

/*
 * update_tabs_visibility: Hide tabs if there is only one tab
 * and the pref is not set.
 * HACK We need to show tabs before inserting the second. Otherwise
 * gtknotebook go crazy.
 */
static void
update_tabs_visibility (GulNotebook *nb, gboolean before_inserting)
{
      gboolean show_tabs;
      guint tabs_num = 1;
      
      if (before_inserting) tabs_num--;
      
      show_tabs = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), tabs_num)
            || nb->priv->policy == GTK_POLICY_ALWAYS;
      
      gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), show_tabs);
}

void
gul_notebook_insert_page (GulNotebook *nb,
                    GtkWidget *child,
                    int position,
                    gboolean jump_to)
{
      GtkWidget *tab_hbox;
      GtkWidget *label;
      
      tab_hbox = tab_build_label (nb, child);
            
      update_tabs_visibility (nb, TRUE);

      if (position == GUL_NOTEBOOK_INSERT_GROUPED)
      {
            /* Keep a list of newly opened tabs, if the list is empty open the new
             * tab after the current one. If it's not, add it after the newly
             * opened tabs.
             */
            if (nb->priv->opened_tabs != NULL)
            {
                  GList *last = g_list_last (nb->priv->opened_tabs);
                  GtkWidget *last_tab = last->data;
                  position = gtk_notebook_page_num 
                            (GTK_NOTEBOOK (nb), last_tab) + 1;
            }
            else
            {
                  position = gtk_notebook_get_current_page 
                            (GTK_NOTEBOOK (nb)) + 1;
            }
            nb->priv->opened_tabs = 
                  g_list_append (nb->priv->opened_tabs, child);
      }
      
      label = gtk_label_new (_("Untitled"));
      gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
      gtk_notebook_insert_page_menu (GTK_NOTEBOOK (nb), 
                               child,
                               tab_hbox, 
                               label, 
                               position);

      if (jump_to)
      {
            nb->priv->automatic_tab_switch = TRUE;

            gtk_notebook_set_current_page (GTK_NOTEBOOK (nb),
                                     position);

            nb->priv->automatic_tab_switch = FALSE;
      }

      g_signal_emit (G_OBJECT (nb), gul_notebook_signals[TAB_ADDED], 
                   0, child);
}

static void
smart_tab_switching_on_closure (GulNotebook *nb,
                        GtkWidget *child)
{
      if (!nb->priv->focused_pages)
      {
            gtk_notebook_next_page (GTK_NOTEBOOK (nb));
      }
      else
      {
            GList *l;
            GtkWidget *child;
            int page_num;

            /* activate the last focused tab */
            l = g_list_last (nb->priv->focused_pages);
            child = GTK_WIDGET (l->data);
            page_num = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
                                      child);

            nb->priv->automatic_tab_switch = TRUE;
            
            gtk_notebook_set_current_page 
                  (GTK_NOTEBOOK (nb), page_num);

            nb->priv->automatic_tab_switch = FALSE;
      }
}

void
gul_notebook_remove_page (GulNotebook *nb,
                    GtkWidget *child)
{
      int position, cur;
      gboolean last_tab;
      GtkWidget *label, *ebox;

      last_tab = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) == 1;
      if (last_tab)
      {
            GtkWidget *window;
            window = gtk_widget_get_toplevel (GTK_WIDGET (nb));
            gul_gui_widget_hide_now (window);
            gtk_widget_destroy (window);
            return;
      }
      
      /* Remove the page from the focused pages list */
      nb->priv->focused_pages =  g_list_remove (nb->priv->focused_pages, 
                                      child);
      nb->priv->opened_tabs = g_list_remove (nb->priv->opened_tabs, child);
            
            
      position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), 
                                child);
      
      cur = gtk_notebook_get_current_page (GTK_NOTEBOOK (nb));
      if (position == cur)
      {
            smart_tab_switching_on_closure (nb, child);
      }
      
      /* remove the tool tip for this ebox */
      label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb), child);
      ebox = GTK_WIDGET (g_object_get_data (G_OBJECT (label), "label-ebox"));
      gtk_tooltips_set_tip (_gul_notebook_tooltips, ebox, NULL, NULL);
 
      /* If we don't clear the icon then for some reason it gets reset back
       * to an empty icon. This means that although gul_notebook_move_page() has
       * a ref to the icon, when it is placed in the new notebook, it is empty */
      gul_notebook_set_page_icon (nb, child, 0);

      /**
       * we ref the child so that it's still alive while the tabs_removed
       * signal is processed.
       */
      g_object_ref (child);
 
      gtk_notebook_remove_page (GTK_NOTEBOOK (nb), position);

      update_tabs_visibility (nb, FALSE);
 
      g_signal_emit (G_OBJECT (nb), gul_notebook_signals[TAB_REMOVED], 
                   0, child);
 
      g_object_unref (child);
}

void
gul_notebook_set_page_color (GulNotebook *nb,
                       GtkWidget *child,
                       GdkColor *color)
{
      GtkWidget *label;

      label = tab_get_label (nb, child);
      gtk_widget_modify_fg (label, GTK_STATE_NORMAL, color);
      gtk_widget_modify_fg (label, GTK_STATE_ACTIVE, color);

      label = gtk_notebook_get_menu_label (GTK_NOTEBOOK (nb), child);
      gtk_widget_modify_fg (label, GTK_STATE_NORMAL, color);
      gtk_widget_modify_fg (label, GTK_STATE_ACTIVE, color);
}

void
gul_notebook_set_page_title (GulNotebook *nb,
                       GtkWidget *child,
                       const char *title)
{
      GtkWidget *label, *ebox, *tab_label;
      gchar *short_title;

      tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb), child);

      ebox = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "label-ebox"));
      label = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "label"));
      short_title = gul_string_shorten (title, MENU_ITEM_MAX_LENGTH);

      gtk_label_set_label (GTK_LABEL (label), title);
      gtk_notebook_set_menu_label_text (GTK_NOTEBOOK (nb), child, short_title);
      gtk_tooltips_set_tip (_gul_notebook_tooltips, ebox, title, NULL);

      g_free (short_title);
}


void
gul_notebook_set_page_icon (GulNotebook *nb,
                      GtkWidget *child,
                      GtkWidget *icon)
{
      GtkWidget *old_icon;
      GtkWidget *label, *hbox;

      label    = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb), child);
      hbox     = GTK_WIDGET (g_object_get_data (G_OBJECT (label), "label-hbox"));
      old_icon = GTK_WIDGET (g_object_get_data (G_OBJECT (label), "icon"));

      if (icon == old_icon) return;

      if (old_icon)
      {
            gtk_container_remove (GTK_CONTAINER (hbox), old_icon);
      }

      if (icon)
      {
            gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
      }

      g_object_set_data (G_OBJECT (label), "icon", icon);
}

void
gul_notebook_set_policy (GulNotebook *nb,
                     GtkPolicyType policy)
{
      if (policy == GTK_POLICY_NEVER)
      {
            /* FIXME this is because of the HACK in update_tabs_visibility,
             * should really get it fixed (or verify it's fixed) instead of
             * working around it, but I don't the details how it goes crazy
             */
            g_warning ("GTK_POLICY_NEVER not implemented");
            return;
      }
      nb->priv->policy = policy;
      update_tabs_visibility (nb, FALSE);
}

Generated by  Doxygen 1.6.0   Back to index