2011-02-24 04:14:30 +01:00
|
|
|
/* file included in chanview.c */
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
GtkWidget *outer; /* outer box */
|
|
|
|
GtkWidget *inner; /* inner box */
|
|
|
|
GtkWidget *b1; /* button1 */
|
|
|
|
GtkWidget *b2; /* button2 */
|
|
|
|
} tabview;
|
|
|
|
|
|
|
|
static void chanview_populate (chanview *cv);
|
|
|
|
|
|
|
|
/* ignore "toggled" signal? */
|
|
|
|
static int ignore_toggle = FALSE;
|
|
|
|
static int tab_left_is_moving = 0;
|
|
|
|
static int tab_right_is_moving = 0;
|
|
|
|
|
|
|
|
/* userdata for gobjects used here:
|
|
|
|
*
|
|
|
|
* tab (togglebuttons inside boxes):
|
|
|
|
* "u" userdata passed to tab-focus callback function (sess)
|
|
|
|
* "c" the tab's (chan *)
|
|
|
|
*
|
|
|
|
* box (family box)
|
|
|
|
* "f" family
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* GtkViewports request at least as much space as their children do.
|
|
|
|
* If we don't intervene here, the GtkViewport will be granted its
|
|
|
|
* request, even at the expense of resizing the top-level window.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
cv_tabs_sizerequest (GtkWidget *viewport, GtkRequisition *requisition, chanview *cv)
|
|
|
|
{
|
|
|
|
if (!cv->vertical)
|
|
|
|
requisition->width = 1;
|
|
|
|
else
|
|
|
|
requisition->height = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv)
|
|
|
|
{
|
|
|
|
GtkAdjustment *adj;
|
|
|
|
GtkWidget *inner;
|
|
|
|
gint viewport_size;
|
|
|
|
|
|
|
|
inner = ((tabview *)cv)->inner;
|
|
|
|
|
|
|
|
if (cv->vertical)
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (adj->upper <= viewport_size)
|
|
|
|
{
|
|
|
|
gtk_widget_hide (((tabview *)cv)->b1);
|
|
|
|
gtk_widget_hide (((tabview *)cv)->b2);
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
gtk_widget_show (((tabview *)cv)->b1);
|
|
|
|
gtk_widget_show (((tabview *)cv)->b2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static gint
|
|
|
|
tab_search_offset (GtkWidget *inner, gint start_offset,
|
|
|
|
gboolean forward, gboolean vertical)
|
|
|
|
{
|
|
|
|
GList *boxes;
|
|
|
|
GList *tabs;
|
|
|
|
GtkWidget *box;
|
|
|
|
GtkWidget *button;
|
|
|
|
gint found;
|
|
|
|
|
|
|
|
boxes = GTK_BOX (inner)->children;
|
|
|
|
if (!forward && boxes)
|
|
|
|
boxes = g_list_last (boxes);
|
|
|
|
|
|
|
|
while (boxes)
|
|
|
|
{
|
|
|
|
box = ((GtkBoxChild *)boxes->data)->widget;
|
|
|
|
boxes = (forward ? boxes->next : boxes->prev);
|
|
|
|
|
|
|
|
tabs = GTK_BOX (box)->children;
|
|
|
|
if (!forward && tabs)
|
|
|
|
tabs = g_list_last (tabs);
|
|
|
|
|
|
|
|
while (tabs)
|
|
|
|
{
|
|
|
|
button = ((GtkBoxChild *)tabs->data)->widget;
|
|
|
|
tabs = (forward ? tabs->next : tabs->prev);
|
|
|
|
|
|
|
|
if (!GTK_IS_TOGGLE_BUTTON (button))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
found = (vertical ? button->allocation.y : button->allocation.x);
|
|
|
|
if ((forward && found > start_offset) ||
|
|
|
|
(!forward && found < start_offset))
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv)
|
|
|
|
{
|
|
|
|
GtkAdjustment *adj;
|
|
|
|
gint viewport_size;
|
|
|
|
gfloat new_value;
|
|
|
|
GtkWidget *inner;
|
|
|
|
gfloat i;
|
|
|
|
|
|
|
|
inner = ((tabview *)cv)->inner;
|
|
|
|
|
|
|
|
if (cv->vertical)
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
new_value = tab_search_offset (inner, adj->value, 0, cv->vertical);
|
|
|
|
|
|
|
|
if (new_value + viewport_size > adj->upper)
|
|
|
|
new_value = adj->upper - viewport_size;
|
|
|
|
|
|
|
|
if (!tab_left_is_moving)
|
|
|
|
{
|
|
|
|
tab_left_is_moving = 1;
|
|
|
|
|
|
|
|
for (i = adj->value; ((i > new_value) && (tab_left_is_moving)); i -= 0.1)
|
|
|
|
{
|
|
|
|
gtk_adjustment_set_value (adj, i);
|
2011-02-28 18:59:32 +01:00
|
|
|
while (g_main_context_pending (NULL))
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
2011-02-24 04:14:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
gtk_adjustment_set_value (adj, new_value);
|
|
|
|
|
|
|
|
tab_left_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tab_left_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
|
|
|
|
{
|
|
|
|
GtkAdjustment *adj;
|
|
|
|
gint viewport_size;
|
|
|
|
gfloat new_value;
|
|
|
|
GtkWidget *inner;
|
|
|
|
gfloat i;
|
|
|
|
|
|
|
|
inner = ((tabview *)cv)->inner;
|
|
|
|
|
|
|
|
if (cv->vertical)
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
|
|
|
|
gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
new_value = tab_search_offset (inner, adj->value, 1, cv->vertical);
|
|
|
|
|
|
|
|
if (new_value == 0 || new_value + viewport_size > adj->upper)
|
|
|
|
new_value = adj->upper - viewport_size;
|
|
|
|
|
|
|
|
if (!tab_right_is_moving)
|
|
|
|
{
|
|
|
|
tab_right_is_moving = 1;
|
|
|
|
|
|
|
|
for (i = adj->value; ((i < new_value) && (tab_right_is_moving)); i += 0.1)
|
|
|
|
{
|
|
|
|
gtk_adjustment_set_value (adj, i);
|
2011-02-28 18:59:32 +01:00
|
|
|
while (g_main_context_pending (NULL))
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
2011-02-24 04:14:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
gtk_adjustment_set_value (adj, new_value);
|
|
|
|
|
|
|
|
tab_right_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tab_right_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv)
|
|
|
|
{
|
|
|
|
/* mouse wheel scrolling */
|
|
|
|
if (event->direction == GDK_SCROLL_UP)
|
|
|
|
tab_scroll_left_up_clicked (widget, cv);
|
|
|
|
else if (event->direction == GDK_SCROLL_DOWN)
|
|
|
|
tab_scroll_right_down_clicked (widget, cv);
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_xclick_cb (GtkWidget *button, chanview *cv)
|
|
|
|
{
|
|
|
|
cv->cb_xbutton (cv, cv->focused, cv->focused->tag, cv->focused->userdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make a Scroll (arrow) button */
|
|
|
|
|
|
|
|
static GtkWidget *
|
|
|
|
make_sbutton (GtkArrowType type, void *click_cb, void *userdata)
|
|
|
|
{
|
|
|
|
GtkWidget *button, *arrow;
|
|
|
|
|
|
|
|
button = gtk_button_new ();
|
|
|
|
arrow = gtk_arrow_new (type, GTK_SHADOW_NONE);
|
|
|
|
gtk_container_add (GTK_CONTAINER (button), arrow);
|
|
|
|
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
|
|
|
|
g_signal_connect (G_OBJECT (button), "clicked",
|
|
|
|
G_CALLBACK (click_cb), userdata);
|
|
|
|
g_signal_connect (G_OBJECT (button), "scroll_event",
|
|
|
|
G_CALLBACK (tab_scroll_cb), userdata);
|
|
|
|
gtk_widget_show (arrow);
|
|
|
|
|
|
|
|
return button;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_init (chanview *cv)
|
|
|
|
{
|
|
|
|
GtkWidget *box, *hbox = NULL;
|
|
|
|
GtkWidget *viewport;
|
|
|
|
GtkWidget *outer;
|
|
|
|
GtkWidget *button;
|
|
|
|
|
|
|
|
if (cv->vertical)
|
|
|
|
outer = gtk_vbox_new (0, 0);
|
|
|
|
else
|
|
|
|
outer = gtk_hbox_new (0, 0);
|
|
|
|
((tabview *)cv)->outer = outer;
|
|
|
|
g_signal_connect (G_OBJECT (outer), "size_allocate",
|
|
|
|
G_CALLBACK (cv_tabs_sizealloc), cv);
|
|
|
|
/* gtk_container_set_border_width (GTK_CONTAINER (outer), 2);*/
|
|
|
|
gtk_widget_show (outer);
|
|
|
|
|
|
|
|
viewport = gtk_viewport_new (0, 0);
|
|
|
|
gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
|
|
|
|
g_signal_connect (G_OBJECT (viewport), "size_request",
|
|
|
|
G_CALLBACK (cv_tabs_sizerequest), cv);
|
|
|
|
g_signal_connect (G_OBJECT (viewport), "scroll_event",
|
|
|
|
G_CALLBACK (tab_scroll_cb), cv);
|
|
|
|
gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0);
|
|
|
|
gtk_widget_show (viewport);
|
|
|
|
|
|
|
|
if (cv->vertical)
|
|
|
|
box = gtk_vbox_new (FALSE, 0);
|
|
|
|
else
|
|
|
|
box = gtk_hbox_new (FALSE, 0);
|
|
|
|
((tabview *)cv)->inner = box;
|
|
|
|
gtk_container_add (GTK_CONTAINER (viewport), box);
|
|
|
|
gtk_widget_show (box);
|
|
|
|
|
|
|
|
/* if vertical, the buttons can be side by side */
|
|
|
|
if (cv->vertical)
|
|
|
|
{
|
|
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
|
|
gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0);
|
|
|
|
gtk_widget_show (hbox);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make the Scroll buttons */
|
|
|
|
((tabview *)cv)->b2 = make_sbutton (cv->vertical ?
|
|
|
|
GTK_ARROW_UP : GTK_ARROW_LEFT,
|
|
|
|
tab_scroll_left_up_clicked,
|
|
|
|
cv);
|
|
|
|
|
|
|
|
((tabview *)cv)->b1 = make_sbutton (cv->vertical ?
|
|
|
|
GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
|
|
|
|
tab_scroll_right_down_clicked,
|
|
|
|
cv);
|
|
|
|
|
|
|
|
if (hbox)
|
|
|
|
{
|
|
|
|
gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2);
|
|
|
|
gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1);
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0);
|
|
|
|
gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
button = gtkutil_button (outer, GTK_STOCK_CLOSE, NULL, cv_tabs_xclick_cb,
|
|
|
|
cv, 0);
|
|
|
|
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
|
|
|
|
GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
|
|
|
|
|
|
|
|
gtk_container_add (GTK_CONTAINER (cv->box), outer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_postinit (chanview *cv)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tab_add_sorted (chanview *cv, GtkWidget *box, GtkWidget *tab, chan *ch)
|
|
|
|
{
|
|
|
|
GList *list;
|
|
|
|
GtkBoxChild *child;
|
|
|
|
int i = 0;
|
|
|
|
void *b;
|
|
|
|
|
|
|
|
if (!cv->sorted)
|
|
|
|
{
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
|
|
|
|
gtk_widget_show (tab);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sorting TODO:
|
|
|
|
* - move tab if renamed (dialogs) */
|
|
|
|
|
|
|
|
/* userdata, passed to mg_tabs_compare() */
|
|
|
|
b = ch->userdata;
|
|
|
|
|
|
|
|
list = GTK_BOX (box)->children;
|
|
|
|
while (list)
|
|
|
|
{
|
|
|
|
child = list->data;
|
|
|
|
if (!GTK_IS_SEPARATOR (child->widget))
|
|
|
|
{
|
|
|
|
void *a = g_object_get_data (G_OBJECT (child->widget), "u");
|
|
|
|
|
|
|
|
if (ch->tag == 0 && cv->cb_compare (a, b) > 0)
|
|
|
|
{
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (box), tab, i);
|
|
|
|
gtk_widget_show (tab);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* append */
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (box), tab, i);
|
|
|
|
gtk_widget_show (tab);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove empty boxes and separators */
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_prune (chanview *cv)
|
|
|
|
{
|
|
|
|
GList *boxes, *children;
|
|
|
|
GtkWidget *box, *inner;
|
|
|
|
GtkBoxChild *child;
|
|
|
|
int empty;
|
|
|
|
|
|
|
|
inner = ((tabview *)cv)->inner;
|
|
|
|
boxes = GTK_BOX (inner)->children;
|
|
|
|
while (boxes)
|
|
|
|
{
|
|
|
|
child = boxes->data;
|
|
|
|
box = child->widget;
|
|
|
|
boxes = boxes->next;
|
|
|
|
|
|
|
|
/* check if the box is empty (except a vseperator) */
|
|
|
|
empty = TRUE;
|
|
|
|
children = GTK_BOX (box)->children;
|
|
|
|
while (children)
|
|
|
|
{
|
|
|
|
if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
|
|
|
|
{
|
|
|
|
empty = FALSE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
children = children->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty)
|
|
|
|
gtk_widget_destroy (box);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tab_add_real (chanview *cv, GtkWidget *tab, chan *ch)
|
|
|
|
{
|
|
|
|
GList *boxes, *children;
|
|
|
|
GtkWidget *sep, *box, *inner;
|
|
|
|
GtkBoxChild *child;
|
|
|
|
int empty;
|
|
|
|
|
|
|
|
inner = ((tabview *)cv)->inner;
|
|
|
|
/* see if a family for this tab already exists */
|
|
|
|
boxes = GTK_BOX (inner)->children;
|
|
|
|
while (boxes)
|
|
|
|
{
|
|
|
|
child = boxes->data;
|
|
|
|
box = child->widget;
|
|
|
|
|
|
|
|
if (g_object_get_data (G_OBJECT (box), "f") == ch->family)
|
|
|
|
{
|
|
|
|
tab_add_sorted (cv, box, tab, ch);
|
|
|
|
gtk_widget_queue_resize (inner->parent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
boxes = boxes->next;
|
|
|
|
|
|
|
|
/* check if the box is empty (except a vseperator) */
|
|
|
|
empty = TRUE;
|
|
|
|
children = GTK_BOX (box)->children;
|
|
|
|
while (children)
|
|
|
|
{
|
|
|
|
if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
|
|
|
|
{
|
|
|
|
empty = FALSE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
children = children->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty)
|
|
|
|
gtk_widget_destroy (box);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create a new family box */
|
|
|
|
if (cv->vertical)
|
|
|
|
{
|
|
|
|
/* vertical */
|
|
|
|
box = gtk_vbox_new (FALSE, 0);
|
|
|
|
sep = gtk_hseparator_new ();
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
/* horiz */
|
|
|
|
box = gtk_hbox_new (FALSE, 0);
|
|
|
|
sep = gtk_vseparator_new ();
|
|
|
|
}
|
|
|
|
|
|
|
|
gtk_box_pack_end (GTK_BOX (box), sep, 0, 0, 4);
|
|
|
|
gtk_widget_show (sep);
|
|
|
|
gtk_box_pack_start (GTK_BOX (inner), box, 0, 0, 0);
|
|
|
|
g_object_set_data (G_OBJECT (box), "f", ch->family);
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
|
|
|
|
gtk_widget_show (tab);
|
|
|
|
gtk_widget_show (box);
|
|
|
|
gtk_widget_queue_resize (inner->parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
tab_ignore_cb (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* called when a tab is clicked (button down) */
|
|
|
|
|
|
|
|
static void
|
|
|
|
tab_pressed_cb (GtkToggleButton *tab, chan *ch)
|
|
|
|
{
|
|
|
|
chan *old_tab;
|
|
|
|
int is_switching = TRUE;
|
|
|
|
chanview *cv = ch->cv;
|
|
|
|
|
|
|
|
ignore_toggle = TRUE;
|
|
|
|
/* de-activate the old tab */
|
|
|
|
old_tab = cv->focused;
|
|
|
|
if (old_tab && old_tab->impl)
|
|
|
|
{
|
|
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_tab->impl), FALSE);
|
|
|
|
if (old_tab == ch)
|
|
|
|
is_switching = FALSE;
|
|
|
|
}
|
|
|
|
gtk_toggle_button_set_active (tab, TRUE);
|
|
|
|
ignore_toggle = FALSE;
|
|
|
|
cv->focused = ch;
|
|
|
|
|
|
|
|
if (/*tab->active*/is_switching)
|
|
|
|
/* call the focus callback */
|
|
|
|
cv->cb_focus (cv, ch, ch->tag, ch->userdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* called for keyboard tab toggles only */
|
|
|
|
static void
|
|
|
|
tab_toggled_cb (GtkToggleButton *tab, chan *ch)
|
|
|
|
{
|
|
|
|
if (ignore_toggle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* activated a tab via keyboard */
|
|
|
|
tab_pressed_cb (tab, ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch)
|
|
|
|
{
|
|
|
|
return ch->cv->cb_contextmenu (ch->cv, ch, ch->tag, ch->userdata, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
|
|
|
|
{
|
|
|
|
GtkWidget *but;
|
|
|
|
|
|
|
|
but = gtk_toggle_button_new_with_label (name);
|
|
|
|
gtk_widget_set_name (but, "xchat-tab");
|
|
|
|
g_object_set_data (G_OBJECT (but), "c", ch);
|
|
|
|
/* used to trap right-clicks */
|
|
|
|
g_signal_connect (G_OBJECT (but), "button_press_event",
|
|
|
|
G_CALLBACK (tab_click_cb), ch);
|
|
|
|
/* avoid prelights */
|
|
|
|
g_signal_connect (G_OBJECT (but), "enter_notify_event",
|
|
|
|
G_CALLBACK (tab_ignore_cb), NULL);
|
|
|
|
g_signal_connect (G_OBJECT (but), "leave_notify_event",
|
|
|
|
G_CALLBACK (tab_ignore_cb), NULL);
|
|
|
|
g_signal_connect (G_OBJECT (but), "pressed",
|
|
|
|
G_CALLBACK (tab_pressed_cb), ch);
|
|
|
|
/* for keyboard */
|
|
|
|
g_signal_connect (G_OBJECT (but), "toggled",
|
|
|
|
G_CALLBACK (tab_toggled_cb), ch);
|
|
|
|
g_object_set_data (G_OBJECT (but), "u", ch->userdata);
|
|
|
|
|
|
|
|
tab_add_real (cv, but, ch);
|
|
|
|
|
|
|
|
return but;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* traverse all the family boxes of tabs
|
|
|
|
*
|
|
|
|
* A "group" is basically:
|
|
|
|
* GtkV/HBox
|
|
|
|
* `-GtkViewPort
|
|
|
|
* `-GtkV/HBox (inner box)
|
|
|
|
* `- GtkBox (family box)
|
|
|
|
* `- GtkToggleButton
|
|
|
|
* `- GtkToggleButton
|
|
|
|
* `- ...
|
|
|
|
* `- GtkBox
|
|
|
|
* `- GtkToggleButton
|
|
|
|
* `- GtkToggleButton
|
|
|
|
* `- ...
|
|
|
|
* `- ...
|
|
|
|
*
|
|
|
|
* */
|
|
|
|
|
|
|
|
static int
|
|
|
|
tab_group_for_each_tab (chanview *cv,
|
|
|
|
int (*callback) (GtkWidget *tab, int num, int usernum),
|
|
|
|
int usernum)
|
|
|
|
{
|
|
|
|
GList *tabs;
|
|
|
|
GList *boxes;
|
|
|
|
GtkBoxChild *child;
|
|
|
|
GtkBox *innerbox;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
innerbox = (GtkBox *) ((tabview *)cv)->inner;
|
|
|
|
boxes = innerbox->children;
|
|
|
|
i = 0;
|
|
|
|
while (boxes)
|
|
|
|
{
|
|
|
|
child = boxes->data;
|
|
|
|
tabs = GTK_BOX (child->widget)->children;
|
|
|
|
|
|
|
|
while (tabs)
|
|
|
|
{
|
|
|
|
child = tabs->data;
|
|
|
|
|
|
|
|
if (!GTK_IS_SEPARATOR (child->widget))
|
|
|
|
{
|
|
|
|
if (callback (child->widget, i, usernum) != -1)
|
|
|
|
return i;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
tabs = tabs->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
boxes = boxes->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tab_check_focus_cb (GtkWidget *tab, int num, int unused)
|
|
|
|
{
|
|
|
|
if (GTK_TOGGLE_BUTTON (tab)->active)
|
|
|
|
return num;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns the currently focused tab number */
|
|
|
|
|
|
|
|
static int
|
|
|
|
tab_group_get_cur_page (chanview *cv)
|
|
|
|
{
|
|
|
|
return tab_group_for_each_tab (cv, tab_check_focus_cb, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_focus (chan *ch)
|
|
|
|
{
|
|
|
|
if (ch->impl)
|
|
|
|
/* focus the new one (tab_pressed_cb defocuses the old one) */
|
|
|
|
tab_pressed_cb (GTK_TOGGLE_BUTTON (ch->impl), ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tab_focus_num_cb (GtkWidget *tab, int num, int want)
|
|
|
|
{
|
|
|
|
if (num == want)
|
|
|
|
{
|
|
|
|
cv_tabs_focus (g_object_get_data (G_OBJECT (tab), "c"));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_change_orientation (chanview *cv)
|
|
|
|
{
|
|
|
|
/* cleanup the old one */
|
|
|
|
if (cv->func_cleanup)
|
|
|
|
cv->func_cleanup (cv);
|
|
|
|
|
|
|
|
/* now rebuild a new tabbar or tree */
|
|
|
|
cv->func_init (cv);
|
|
|
|
chanview_populate (cv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* switch to the tab number specified */
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_move_focus (chanview *cv, gboolean relative, int num)
|
|
|
|
{
|
|
|
|
int i, max;
|
|
|
|
|
|
|
|
if (relative)
|
|
|
|
{
|
|
|
|
max = cv->size;
|
|
|
|
i = tab_group_get_cur_page (cv) + num;
|
|
|
|
/* make it wrap around at both ends */
|
|
|
|
if (i < 0)
|
|
|
|
i = max - 1;
|
|
|
|
if (i >= max)
|
|
|
|
i = 0;
|
|
|
|
tab_group_for_each_tab (cv, tab_focus_num_cb, i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tab_group_for_each_tab (cv, tab_focus_num_cb, num);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_remove (chan *ch)
|
|
|
|
{
|
|
|
|
gtk_widget_destroy (ch->impl);
|
|
|
|
ch->impl = NULL;
|
|
|
|
|
|
|
|
cv_tabs_prune (ch->cv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_move (chan *ch, int delta)
|
|
|
|
{
|
|
|
|
int i, pos = 0;
|
|
|
|
GList *list;
|
|
|
|
GtkWidget *parent = ((GtkWidget *)ch->impl)->parent;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
for (list = GTK_BOX (parent)->children; list; list = list->next)
|
|
|
|
{
|
|
|
|
GtkBoxChild *child_entry;
|
|
|
|
|
|
|
|
child_entry = list->data;
|
|
|
|
if (child_entry->widget == ch->impl)
|
|
|
|
pos = i;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = (pos - delta) % i;
|
|
|
|
gtk_box_reorder_child (GTK_BOX (parent), ch->impl, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_move_family (chan *ch, int delta)
|
|
|
|
{
|
|
|
|
int i, pos = 0;
|
|
|
|
GList *list;
|
|
|
|
GtkWidget *box = NULL;
|
|
|
|
|
|
|
|
/* find position of tab's family */
|
|
|
|
i = 0;
|
|
|
|
for (list = GTK_BOX (((tabview *)ch->cv)->inner)->children; list; list = list->next)
|
|
|
|
{
|
|
|
|
GtkBoxChild *child_entry;
|
|
|
|
void *fam;
|
|
|
|
|
|
|
|
child_entry = list->data;
|
|
|
|
fam = g_object_get_data (G_OBJECT (child_entry->widget), "f");
|
|
|
|
if (fam == ch->family)
|
|
|
|
{
|
|
|
|
box = child_entry->widget;
|
|
|
|
pos = i;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = (pos - delta) % i;
|
|
|
|
gtk_box_reorder_child (GTK_BOX (box->parent), box, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_cleanup (chanview *cv)
|
|
|
|
{
|
|
|
|
if (cv->box)
|
|
|
|
gtk_widget_destroy (((tabview *)cv)->outer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_set_color (chan *ch, PangoAttrList *list)
|
|
|
|
{
|
|
|
|
gtk_label_set_attributes (GTK_LABEL (GTK_BIN (ch->impl)->child), list);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cv_tabs_rename (chan *ch, char *name)
|
|
|
|
{
|
|
|
|
PangoAttrList *attr;
|
|
|
|
GtkWidget *tab = ch->impl;
|
|
|
|
|
|
|
|
attr = gtk_label_get_attributes (GTK_LABEL (GTK_BIN (tab)->child));
|
|
|
|
if (attr)
|
|
|
|
pango_attr_list_ref (attr);
|
|
|
|
|
|
|
|
gtk_button_set_label (GTK_BUTTON (tab), name);
|
|
|
|
gtk_widget_queue_resize (tab->parent->parent->parent);
|
|
|
|
|
|
|
|
if (attr)
|
|
|
|
{
|
|
|
|
gtk_label_set_attributes (GTK_LABEL (GTK_BIN (tab)->child), attr);
|
|
|
|
pango_attr_list_unref (attr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
cv_tabs_is_collapsed (chan *ch)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static chan *
|
|
|
|
cv_tabs_get_parent (chan *ch)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|