1 /* abstract channel view: tabs or tree or anything you like */
12 /* treeStore columns */
14 #define COL_NAME 0 /* (char *) */
15 #define COL_CHAN 1 /* (chan *) */
16 #define COL_ATTR 2 /* (PangoAttrList *) */
17 #define COL_PIXBUF 3 /* (GdkPixbuf *) */
21 /* impl scratch area */
22 char implscratch
[sizeof (void *) * 8];
25 int size
; /* number of channels in view */
27 GtkWidget
*box
; /* the box we destroy when changing implementations */
28 GtkStyle
*style
; /* style used for tree */
29 chan
*focused
; /* currently focused channel */
33 void (*cb_focus
) (chanview
*, chan
*, int tag
, void *userdata
);
34 void (*cb_xbutton
) (chanview
*, chan
*, int tag
, void *userdata
);
35 gboolean (*cb_contextmenu
) (chanview
*, chan
*, int tag
, void *userdata
, GdkEventButton
*);
36 int (*cb_compare
) (void *a
, void *b
);
39 void (*func_init
) (chanview
*);
40 void (*func_postinit
) (chanview
*);
41 void *(*func_add
) (chanview
*, chan
*, char *, GtkTreeIter
*);
42 void (*func_move_focus
) (chanview
*, gboolean
, int);
43 void (*func_change_orientation
) (chanview
*);
44 void (*func_remove
) (chan
*);
45 void (*func_move
) (chan
*, int delta
);
46 void (*func_move_family
) (chan
*, int delta
);
47 void (*func_focus
) (chan
*);
48 void (*func_set_color
) (chan
*, PangoAttrList
*);
49 void (*func_rename
) (chan
*, char *);
50 gboolean (*func_is_collapsed
) (chan
*);
51 chan
*(*func_get_parent
) (chan
*);
52 void (*func_cleanup
) (chanview
*);
54 unsigned int sorted
:1;
55 unsigned int vertical
:1;
56 unsigned int use_icons
:1;
61 chanview
*cv
; /* our owner */
63 void *userdata
; /* session * */
64 void *family
; /* server * or null */
65 void *impl
; /* togglebutton or null */
67 short allow_closure
; /* allow it to be closed when it still has children? */
71 static chan
*cv_find_chan_by_number (chanview
*cv
, int num
);
72 static int cv_find_number_of_chan (chanview
*cv
, chan
*find_ch
);
75 /* ======= TABS ======= */
77 #include "chanview-tabs.h"
80 /* ======= TREE ======= */
82 #include "chanview-tree.h"
85 /* ==== ABSTRACT CHANVIEW ==== */
88 truncate_tab_name (char *name
, int max
)
92 if (max
> 2 && g_utf8_strlen (name
, -1) > max
)
94 /* truncate long channel names */
95 buf
= malloc (strlen (name
) + 4);
97 g_utf8_offset_to_pointer (buf
, max
)[0] = 0;
105 /* iterate through a model, into 1 depth of children */
108 model_foreach_1 (GtkTreeModel
*model
, void (*func
)(void *, GtkTreeIter
*),
111 GtkTreeIter iter
, inner
;
113 if (gtk_tree_model_get_iter_first (model
, &iter
))
117 func (userdata
, &iter
);
118 if (gtk_tree_model_iter_children (model
, &inner
, &iter
))
121 func (userdata
, &inner
);
122 while (gtk_tree_model_iter_next (model
, &inner
));
125 while (gtk_tree_model_iter_next (model
, &iter
));
130 chanview_pop_cb (chanview
*cv
, GtkTreeIter
*iter
)
136 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), iter
,
137 COL_NAME
, &name
, COL_CHAN
, &ch
, COL_ATTR
, &attr
, -1);
138 ch
->impl
= cv
->func_add (cv
, ch
, name
, NULL
);
141 cv
->func_set_color (ch
, attr
);
142 pango_attr_list_unref (attr
);
148 chanview_populate (chanview
*cv
)
150 model_foreach_1 (GTK_TREE_MODEL (cv
->store
), (void *)chanview_pop_cb
, cv
);
154 chanview_set_impl (chanview
*cv
, int type
)
156 /* cleanup the old one */
157 if (cv
->func_cleanup
)
158 cv
->func_cleanup (cv
);
163 cv
->func_init
= cv_tabs_init
;
164 cv
->func_postinit
= cv_tabs_postinit
;
165 cv
->func_add
= cv_tabs_add
;
166 cv
->func_move_focus
= cv_tabs_move_focus
;
167 cv
->func_change_orientation
= cv_tabs_change_orientation
;
168 cv
->func_remove
= cv_tabs_remove
;
169 cv
->func_move
= cv_tabs_move
;
170 cv
->func_move_family
= cv_tabs_move_family
;
171 cv
->func_focus
= cv_tabs_focus
;
172 cv
->func_set_color
= cv_tabs_set_color
;
173 cv
->func_rename
= cv_tabs_rename
;
174 cv
->func_is_collapsed
= cv_tabs_is_collapsed
;
175 cv
->func_get_parent
= cv_tabs_get_parent
;
176 cv
->func_cleanup
= cv_tabs_cleanup
;
180 cv
->func_init
= cv_tree_init
;
181 cv
->func_postinit
= cv_tree_postinit
;
182 cv
->func_add
= cv_tree_add
;
183 cv
->func_move_focus
= cv_tree_move_focus
;
184 cv
->func_change_orientation
= cv_tree_change_orientation
;
185 cv
->func_remove
= cv_tree_remove
;
186 cv
->func_move
= cv_tree_move
;
187 cv
->func_move_family
= cv_tree_move_family
;
188 cv
->func_focus
= cv_tree_focus
;
189 cv
->func_set_color
= cv_tree_set_color
;
190 cv
->func_rename
= cv_tree_rename
;
191 cv
->func_is_collapsed
= cv_tree_is_collapsed
;
192 cv
->func_get_parent
= cv_tree_get_parent
;
193 cv
->func_cleanup
= cv_tree_cleanup
;
197 /* now rebuild a new tabbar or tree */
200 chanview_populate (cv
);
202 cv
->func_postinit (cv
);
206 cv
->func_focus (cv
->focused
);
210 chanview_free_ch (chanview
*cv
, GtkTreeIter
*iter
)
214 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), iter
, COL_CHAN
, &ch
, -1);
219 chanview_destroy_store (chanview
*cv
) /* free every (chan *) in the store */
221 model_foreach_1 (GTK_TREE_MODEL (cv
->store
), (void *)chanview_free_ch
, cv
);
222 g_object_unref (cv
->store
);
226 chanview_destroy (chanview
*cv
)
228 if (cv
->func_cleanup
)
229 cv
->func_cleanup (cv
);
232 gtk_widget_destroy (cv
->box
);
234 chanview_destroy_store (cv
);
239 chanview_box_destroy_cb (GtkWidget
*box
, chanview
*cv
)
242 chanview_destroy (cv
);
246 chanview_new (int type
, int trunc_len
, gboolean sort
, gboolean use_icons
,
251 cv
= calloc (1, sizeof (chanview
));
252 cv
->store
= gtk_tree_store_new (4, G_TYPE_STRING
, G_TYPE_POINTER
,
253 PANGO_TYPE_ATTR_LIST
, GDK_TYPE_PIXBUF
);
255 cv
->box
= gtk_hbox_new (0, 0);
256 cv
->trunc_len
= trunc_len
;
258 cv
->use_icons
= use_icons
;
259 gtk_widget_show (cv
->box
);
260 chanview_set_impl (cv
, type
);
262 g_signal_connect (G_OBJECT (cv
->box
), "destroy",
263 G_CALLBACK (chanview_box_destroy_cb
), cv
);
268 /* too lazy for signals */
271 chanview_set_callbacks (chanview
*cv
,
272 void (*cb_focus
) (chanview
*, chan
*, int tag
, void *userdata
),
273 void (*cb_xbutton
) (chanview
*, chan
*, int tag
, void *userdata
),
274 gboolean (*cb_contextmenu
) (chanview
*, chan
*, int tag
, void *userdata
, GdkEventButton
*),
275 int (*cb_compare
) (void *a
, void *b
))
277 cv
->cb_focus
= cb_focus
;
278 cv
->cb_xbutton
= cb_xbutton
;
279 cv
->cb_contextmenu
= cb_contextmenu
;
280 cv
->cb_compare
= cb_compare
;
283 /* find a place to insert this new entry, based on the compare function */
286 chanview_insert_sorted (chanview
*cv
, GtkTreeIter
*add_iter
, GtkTreeIter
*parent
, void *ud
)
291 if (cv
->sorted
&& gtk_tree_model_iter_children (GTK_TREE_MODEL (cv
->store
), &iter
, parent
))
295 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), &iter
, COL_CHAN
, &ch
, -1);
296 if (ch
->tag
== 0 && cv
->cb_compare (ch
->userdata
, ud
) > 0)
298 gtk_tree_store_insert_before (cv
->store
, add_iter
, parent
, &iter
);
302 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), &iter
));
305 gtk_tree_store_append (cv
->store
, add_iter
, parent
);
308 /* find a parent node with the same "family" pointer (i.e. the Server tab) */
311 chanview_find_parent (chanview
*cv
, void *family
, GtkTreeIter
*search_iter
, chan
*avoid
)
315 /* find this new row's parent, if any */
316 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv
->store
), search_iter
))
320 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), search_iter
,
321 COL_CHAN
, &search_ch
, -1);
322 if (family
== search_ch
->family
&& search_ch
!= avoid
/*&&
323 gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/)
326 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), search_iter
));
333 chanview_add_real (chanview
*cv
, char *name
, void *family
, void *userdata
,
334 gboolean allow_closure
, int tag
, GdkPixbuf
*icon
,
335 chan
*ch
, chan
*avoid
)
337 GtkTreeIter parent_iter
;
339 gboolean has_parent
= FALSE
;
341 if (chanview_find_parent (cv
, family
, &parent_iter
, avoid
))
343 chanview_insert_sorted (cv
, &iter
, &parent_iter
, userdata
);
347 gtk_tree_store_append (cv
->store
, &iter
, NULL
);
352 ch
= calloc (1, sizeof (chan
));
353 ch
->userdata
= userdata
;
356 ch
->allow_closure
= allow_closure
;
360 memcpy (&(ch
->iter
), &iter
, sizeof (iter
));
362 gtk_tree_store_set (cv
->store
, &iter
, COL_NAME
, name
, COL_CHAN
, ch
,
363 COL_PIXBUF
, icon
, -1);
367 ch
->impl
= cv
->func_add (cv
, ch
, name
, NULL
);
369 ch
->impl
= cv
->func_add (cv
, ch
, name
, &parent_iter
);
375 chanview_add (chanview
*cv
, char *name
, void *family
, void *userdata
, gboolean allow_closure
, int tag
, GdkPixbuf
*icon
)
380 new_name
= truncate_tab_name (name
, cv
->trunc_len
);
382 ret
= chanview_add_real (cv
, new_name
, family
, userdata
, allow_closure
, tag
, icon
, NULL
, NULL
);
384 if (new_name
!= name
)
391 chanview_get_size (chanview
*cv
)
397 chanview_get_box (chanview
*cv
)
403 chanview_move_focus (chanview
*cv
, gboolean relative
, int num
)
405 cv
->func_move_focus (cv
, relative
, num
);
409 chanview_get_orientation (chanview
*cv
)
411 return (cv
->vertical
? GTK_ORIENTATION_VERTICAL
: GTK_ORIENTATION_HORIZONTAL
);
415 chanview_set_orientation (chanview
*cv
, gboolean vertical
)
417 if (vertical
!= cv
->vertical
)
419 cv
->vertical
= vertical
;
420 cv
->func_change_orientation (cv
);
425 chan_get_tag (chan
*ch
)
431 chan_get_userdata (chan
*ch
)
437 chan_focus (chan
*ch
)
439 if (ch
->cv
->focused
== ch
)
442 ch
->cv
->func_focus (ch
);
446 chan_move (chan
*ch
, int delta
)
448 ch
->cv
->func_move (ch
, delta
);
452 chan_move_family (chan
*ch
, int delta
)
454 ch
->cv
->func_move_family (ch
, delta
);
458 chan_set_color (chan
*ch
, PangoAttrList
*list
)
460 gtk_tree_store_set (ch
->cv
->store
, &ch
->iter
, COL_ATTR
, list
, -1);
461 ch
->cv
->func_set_color (ch
, list
);
465 chan_rename (chan
*ch
, char *name
, int trunc_len
)
469 new_name
= truncate_tab_name (name
, trunc_len
);
471 gtk_tree_store_set (ch
->cv
->store
, &ch
->iter
, COL_NAME
, new_name
, -1);
472 ch
->cv
->func_rename (ch
, new_name
);
473 ch
->cv
->trunc_len
= trunc_len
;
475 if (new_name
!= name
)
479 /* this thing is overly complicated */
482 cv_find_number_of_chan (chanview
*cv
, chan
*find_ch
)
484 GtkTreeIter iter
, inner
;
488 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv
->store
), &iter
))
492 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), &iter
, COL_CHAN
, &ch
, -1);
497 if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv
->store
), &inner
, &iter
))
501 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), &inner
, COL_CHAN
, &ch
, -1);
506 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), &inner
));
509 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), &iter
));
512 return 0; /* WARNING */
515 /* this thing is overly complicated too */
518 cv_find_chan_by_number (chanview
*cv
, int num
)
520 GtkTreeIter iter
, inner
;
524 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv
->store
), &iter
))
530 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), &iter
, COL_CHAN
, &ch
, -1);
535 if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv
->store
), &inner
, &iter
))
541 gtk_tree_model_get (GTK_TREE_MODEL (cv
->store
), &inner
, COL_CHAN
, &ch
, -1);
546 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), &inner
));
549 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv
->store
), &iter
));
556 chan_emancipate_children (chan
*ch
)
560 GtkTreeIter childiter
;
563 while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch
->cv
->store
), &childiter
, &ch
->iter
))
565 /* remove and re-add all the children, but avoid using "ch" as parent */
566 gtk_tree_model_get (GTK_TREE_MODEL (ch
->cv
->store
), &childiter
,
567 COL_NAME
, &name
, COL_CHAN
, &childch
, COL_ATTR
, &attr
, -1);
568 ch
->cv
->func_remove (childch
);
569 gtk_tree_store_remove (ch
->cv
->store
, &childiter
);
571 chanview_add_real (childch
->cv
, name
, childch
->family
, childch
->userdata
, childch
->allow_closure
, childch
->tag
, childch
->icon
, childch
, ch
);
574 childch
->cv
->func_set_color (childch
, attr
);
575 pango_attr_list_unref (attr
);
582 chan_remove (chan
*ch
, gboolean force
)
586 extern int xchat_is_quitting
;
588 if (xchat_is_quitting
) /* avoid lots of looping on exit */
591 /* is this ch allowed to be closed while still having children? */
593 gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch
->cv
->store
), &ch
->iter
) &&
597 chan_emancipate_children (ch
);
598 ch
->cv
->func_remove (ch
);
600 /* is it the focused one? */
601 if (ch
->cv
->focused
== ch
)
603 ch
->cv
->focused
= NULL
;
605 /* try to move the focus to some other valid channel */
606 num
= cv_find_number_of_chan (ch
->cv
, ch
);
607 /* move to the one left of the closing tab */
608 new_ch
= cv_find_chan_by_number (ch
->cv
, num
- 1);
609 if (new_ch
&& new_ch
!= ch
)
611 chan_focus (new_ch
); /* this'll will set ch->cv->focused for us too */
614 /* if it fails, try focus from tab 0 and up */
615 for (i
= 0; i
< ch
->cv
->size
; i
++)
617 new_ch
= cv_find_chan_by_number (ch
->cv
, i
);
618 if (new_ch
&& new_ch
!= ch
)
620 chan_focus (new_ch
); /* this'll will set ch->cv->focused for us too */
628 gtk_tree_store_remove (ch
->cv
->store
, &ch
->iter
);
634 chan_is_collapsed (chan
*ch
)
636 return ch
->cv
->func_is_collapsed (ch
);
640 chan_get_parent (chan
*ch
)
642 return ch
->cv
->func_get_parent (ch
);