1 /* HexChat
2  * Copyright (C) 1998-2010 Peter Zelezny.
3  * Copyright (C) 2009-2013 Berke Viktor.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 /* abstract channel view: tabs or tree or anything you like */
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "../common/hexchat.h"
26 #include "../common/hexchatc.h"
27 #include "fe-gtk.h"
28 #include "maingui.h"
29 #include "gtkutil.h"
30 #include "chanview.h"
31 
32 /* treeStore columns */
33 #define COL_NAME 0		/* (char *) */
34 #define COL_CHAN 1		/* (chan *) */
35 #define COL_ATTR 2		/* (PangoAttrList *) */
36 #define COL_PIXBUF 3		/* (GdkPixbuf *) */
37 
38 struct _chanview
39 {
40 	/* impl scratch area */
41 	char implscratch[sizeof (void *) * 8];
42 
43 	GtkTreeStore *store;
44 	int size;			/* number of channels in view */
45 
46 	GtkWidget *box;	/* the box we destroy when changing implementations */
47 	GtkStyle *style;	/* style used for tree */
48 	chan *focused;		/* currently focused channel */
49 	int trunc_len;
50 
51 	/* callbacks */
52 	void (*cb_focus) (chanview *, chan *, int tag, void *userdata);
53 	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata);
54 	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *);
55 	int (*cb_compare) (void *a, void *b);
56 
57 	/* impl */
58 	void (*func_init) (chanview *);
59 	void (*func_postinit) (chanview *);
60 	void *(*func_add) (chanview *, chan *, char *, GtkTreeIter *);
61 	void (*func_move_focus) (chanview *, gboolean, int);
62 	void (*func_change_orientation) (chanview *);
63 	void (*func_remove) (chan *);
64 	void (*func_move) (chan *, int delta);
65 	void (*func_move_family) (chan *, int delta);
66 	void (*func_focus) (chan *);
67 	void (*func_set_color) (chan *, PangoAttrList *);
68 	void (*func_rename) (chan *, char *);
69 	gboolean (*func_is_collapsed) (chan *);
70 	chan *(*func_get_parent) (chan *);
71 	void (*func_cleanup) (chanview *);
72 
73 	unsigned int sorted:1;
74 	unsigned int vertical:1;
75 	unsigned int use_icons:1;
76 };
77 
78 struct _chan
79 {
80 	chanview *cv;	/* our owner */
81 	GtkTreeIter iter;
82 	void *userdata;	/* session * */
83 	void *family;		/* server * or null */
84 	void *impl;	/* togglebutton or null */
85 	GdkPixbuf *icon;
86 	short allow_closure;	/* allow it to be closed when it still has children? */
87 	short tag;
88 };
89 
90 static chan *cv_find_chan_by_number (chanview *cv, int num);
91 static int cv_find_number_of_chan (chanview *cv, chan *find_ch);
92 
93 
94 /* ======= TABS ======= */
95 
96 #include "chanview-tabs.c"
97 
98 
99 /* ======= TREE ======= */
100 
101 #include "chanview-tree.c"
102 
103 
104 /* ==== ABSTRACT CHANVIEW ==== */
105 
106 static char *
truncate_tab_name(char * name,int max)107 truncate_tab_name (char *name, int max)
108 {
109 	char *buf;
110 
111 	if (max > 2 && g_utf8_strlen (name, -1) > max)
112 	{
113 		/* truncate long channel names */
114 		buf = g_malloc (strlen (name) + 4);
115 		g_utf8_strncpy (buf, name, max);
116 		strcat (buf, "..");
117 		return buf;
118 	}
119 
120 	return name;
121 }
122 
123 /* iterate through a model, into 1 depth of children */
124 
125 static void
model_foreach_1(GtkTreeModel * model,void (* func)(void *,GtkTreeIter *),void * userdata)126 model_foreach_1 (GtkTreeModel *model, void (*func)(void *, GtkTreeIter *),
127 					  void *userdata)
128 {
129 	GtkTreeIter iter, inner;
130 
131 	if (gtk_tree_model_get_iter_first (model, &iter))
132 	{
133 		do
134 		{
135 			func (userdata, &iter);
136 			if (gtk_tree_model_iter_children (model, &inner, &iter))
137 			{
138 				do
139 					func (userdata, &inner);
140 				while (gtk_tree_model_iter_next (model, &inner));
141 			}
142 		}
143 		while (gtk_tree_model_iter_next (model, &iter));
144 	}
145 }
146 
147 static void
chanview_pop_cb(chanview * cv,GtkTreeIter * iter)148 chanview_pop_cb (chanview *cv, GtkTreeIter *iter)
149 {
150 	chan *ch;
151 	char *name;
152 	PangoAttrList *attr;
153 
154 	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter,
155 							  COL_NAME, &name, COL_CHAN, &ch, COL_ATTR, &attr, -1);
156 	ch->impl = cv->func_add (cv, ch, name, NULL);
157 	if (attr)
158 	{
159 		cv->func_set_color (ch, attr);
160 		pango_attr_list_unref (attr);
161 	}
162 	g_free (name);
163 }
164 
165 static void
chanview_populate(chanview * cv)166 chanview_populate (chanview *cv)
167 {
168 	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_pop_cb, cv);
169 }
170 
171 void
chanview_set_impl(chanview * cv,int type)172 chanview_set_impl (chanview *cv, int type)
173 {
174 	/* cleanup the old one */
175 	if (cv->func_cleanup)
176 		cv->func_cleanup (cv);
177 
178 	switch (type)
179 	{
180 	case 0:
181 		cv->func_init = cv_tabs_init;
182 		cv->func_postinit = cv_tabs_postinit;
183 		cv->func_add = cv_tabs_add;
184 		cv->func_move_focus = cv_tabs_move_focus;
185 		cv->func_change_orientation = cv_tabs_change_orientation;
186 		cv->func_remove = cv_tabs_remove;
187 		cv->func_move = cv_tabs_move;
188 		cv->func_move_family = cv_tabs_move_family;
189 		cv->func_focus = cv_tabs_focus;
190 		cv->func_set_color = cv_tabs_set_color;
191 		cv->func_rename = cv_tabs_rename;
192 		cv->func_is_collapsed = cv_tabs_is_collapsed;
193 		cv->func_get_parent = cv_tabs_get_parent;
194 		cv->func_cleanup = cv_tabs_cleanup;
195 		break;
196 
197 	default:
198 		cv->func_init = cv_tree_init;
199 		cv->func_postinit = cv_tree_postinit;
200 		cv->func_add = cv_tree_add;
201 		cv->func_move_focus = cv_tree_move_focus;
202 		cv->func_change_orientation = cv_tree_change_orientation;
203 		cv->func_remove = cv_tree_remove;
204 		cv->func_move = cv_tree_move;
205 		cv->func_move_family = cv_tree_move_family;
206 		cv->func_focus = cv_tree_focus;
207 		cv->func_set_color = cv_tree_set_color;
208 		cv->func_rename = cv_tree_rename;
209 		cv->func_is_collapsed = cv_tree_is_collapsed;
210 		cv->func_get_parent = cv_tree_get_parent;
211 		cv->func_cleanup = cv_tree_cleanup;
212 		break;
213 	}
214 
215 	/* now rebuild a new tabbar or tree */
216 	cv->func_init (cv);
217 
218 	chanview_populate (cv);
219 
220 	cv->func_postinit (cv);
221 
222 	/* force re-focus */
223 	if (cv->focused)
224 		cv->func_focus (cv->focused);
225 }
226 
227 static void
chanview_free_ch(chanview * cv,GtkTreeIter * iter)228 chanview_free_ch (chanview *cv, GtkTreeIter *iter)
229 {
230 	chan *ch;
231 
232 	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, COL_CHAN, &ch, -1);
233 	g_free (ch);
234 }
235 
236 static void
chanview_destroy_store(chanview * cv)237 chanview_destroy_store (chanview *cv)	/* free every (chan *) in the store */
238 {
239 	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_free_ch, cv);
240 	g_object_unref (cv->store);
241 }
242 
243 static void
chanview_destroy(chanview * cv)244 chanview_destroy (chanview *cv)
245 {
246 	if (cv->func_cleanup)
247 		cv->func_cleanup (cv);
248 
249 	if (cv->box)
250 		gtk_widget_destroy (cv->box);
251 
252 	chanview_destroy_store (cv);
253 	g_free (cv);
254 }
255 
256 static void
chanview_box_destroy_cb(GtkWidget * box,chanview * cv)257 chanview_box_destroy_cb (GtkWidget *box, chanview *cv)
258 {
259 	cv->box = NULL;
260 	chanview_destroy (cv);
261 }
262 
263 chanview *
chanview_new(int type,int trunc_len,gboolean sort,gboolean use_icons,GtkStyle * style)264 chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
265 				  GtkStyle *style)
266 {
267 	chanview *cv;
268 
269 	cv = g_new0 (chanview, 1);
270 	cv->store = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_POINTER,
271 											  PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF);
272 	cv->style = style;
273 	cv->box = gtk_hbox_new (0, 0);
274 	cv->trunc_len = trunc_len;
275 	cv->sorted = sort;
276 	cv->use_icons = use_icons;
277 	gtk_widget_show (cv->box);
278 	chanview_set_impl (cv, type);
279 
280 	g_signal_connect (G_OBJECT (cv->box), "destroy",
281 							G_CALLBACK (chanview_box_destroy_cb), cv);
282 
283 	return cv;
284 }
285 
286 /* too lazy for signals */
287 
288 void
chanview_set_callbacks(chanview * cv,void (* cb_focus)(chanview *,chan *,int tag,void * userdata),void (* cb_xbutton)(chanview *,chan *,int tag,void * userdata),gboolean (* cb_contextmenu)(chanview *,chan *,int tag,void * userdata,GdkEventButton *),int (* cb_compare)(void * a,void * b))289 chanview_set_callbacks (chanview *cv,
290 	void (*cb_focus) (chanview *, chan *, int tag, void *userdata),
291 	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata),
292 	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
293 	int (*cb_compare) (void *a, void *b))
294 {
295 	cv->cb_focus = cb_focus;
296 	cv->cb_xbutton = cb_xbutton;
297 	cv->cb_contextmenu = cb_contextmenu;
298 	cv->cb_compare = cb_compare;
299 }
300 
301 /* find a place to insert this new entry, based on the compare function */
302 
303 static void
chanview_insert_sorted(chanview * cv,GtkTreeIter * add_iter,GtkTreeIter * parent,void * ud)304 chanview_insert_sorted (chanview *cv, GtkTreeIter *add_iter, GtkTreeIter *parent, void *ud)
305 {
306 	GtkTreeIter iter;
307 	chan *ch;
308 
309 	if (cv->sorted && gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &iter, parent))
310 	{
311 		do
312 		{
313 			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
314 			if (ch->tag == 0 && cv->cb_compare (ch->userdata, ud) > 0)
315 			{
316 				gtk_tree_store_insert_before (cv->store, add_iter, parent, &iter);
317 				return;
318 			}
319 		}
320 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
321 	}
322 
323 	gtk_tree_store_append (cv->store, add_iter, parent);
324 }
325 
326 /* find a parent node with the same "family" pointer (i.e. the Server tab) */
327 
328 static int
chanview_find_parent(chanview * cv,void * family,GtkTreeIter * search_iter,chan * avoid)329 chanview_find_parent (chanview *cv, void *family, GtkTreeIter *search_iter, chan *avoid)
330 {
331 	chan *search_ch;
332 
333 	/* find this new row's parent, if any */
334 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), search_iter))
335 	{
336 		do
337 		{
338 			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), search_iter,
339 									  COL_CHAN, &search_ch, -1);
340 			if (family == search_ch->family && search_ch != avoid /*&&
341 				 gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/)
342 				return TRUE;
343 		}
344 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), search_iter));
345 	}
346 
347 	return FALSE;
348 }
349 
350 static chan *
chanview_add_real(chanview * cv,char * name,void * family,void * userdata,gboolean allow_closure,int tag,GdkPixbuf * icon,chan * ch,chan * avoid)351 chanview_add_real (chanview *cv, char *name, void *family, void *userdata,
352 						 gboolean allow_closure, int tag, GdkPixbuf *icon,
353 						 chan *ch, chan *avoid)
354 {
355 	GtkTreeIter parent_iter;
356 	GtkTreeIter iter;
357 	gboolean has_parent = FALSE;
358 
359 	if (chanview_find_parent (cv, family, &parent_iter, avoid))
360 	{
361 		chanview_insert_sorted (cv, &iter, &parent_iter, userdata);
362 		has_parent = TRUE;
363 	} else
364 	{
365 		gtk_tree_store_append (cv->store, &iter, NULL);
366 	}
367 
368 	if (!ch)
369 	{
370 		ch = g_new0 (chan, 1);
371 		ch->userdata = userdata;
372 		ch->family = family;
373 		ch->cv = cv;
374 		ch->allow_closure = allow_closure;
375 		ch->tag = tag;
376 		ch->icon = icon;
377 	}
378 	memcpy (&(ch->iter), &iter, sizeof (iter));
379 
380 	gtk_tree_store_set (cv->store, &iter, COL_NAME, name, COL_CHAN, ch,
381 							  COL_PIXBUF, icon, -1);
382 
383 	cv->size++;
384 	if (!has_parent)
385 		ch->impl = cv->func_add (cv, ch, name, NULL);
386 	else
387 		ch->impl = cv->func_add (cv, ch, name, &parent_iter);
388 
389 	return ch;
390 }
391 
392 chan *
chanview_add(chanview * cv,char * name,void * family,void * userdata,gboolean allow_closure,int tag,GdkPixbuf * icon)393 chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon)
394 {
395 	char *new_name;
396 	chan *ret;
397 
398 	new_name = truncate_tab_name (name, cv->trunc_len);
399 
400 	ret = chanview_add_real (cv, new_name, family, userdata, allow_closure, tag, icon, NULL, NULL);
401 
402 	if (new_name != name)
403 		g_free (new_name);
404 
405 	return ret;
406 }
407 
408 int
chanview_get_size(chanview * cv)409 chanview_get_size (chanview *cv)
410 {
411 	return cv->size;
412 }
413 
414 GtkWidget *
chanview_get_box(chanview * cv)415 chanview_get_box (chanview *cv)
416 {
417 	return cv->box;
418 }
419 
420 void
chanview_move_focus(chanview * cv,gboolean relative,int num)421 chanview_move_focus (chanview *cv, gboolean relative, int num)
422 {
423 	cv->func_move_focus (cv, relative, num);
424 }
425 
426 GtkOrientation
chanview_get_orientation(chanview * cv)427 chanview_get_orientation (chanview *cv)
428 {
429 	return (cv->vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
430 }
431 
432 void
chanview_set_orientation(chanview * cv,gboolean vertical)433 chanview_set_orientation (chanview *cv, gboolean vertical)
434 {
435 	if (vertical != cv->vertical)
436 	{
437 		cv->vertical = vertical;
438 		cv->func_change_orientation (cv);
439 	}
440 }
441 
442 int
chan_get_tag(chan * ch)443 chan_get_tag (chan *ch)
444 {
445 	return ch->tag;
446 }
447 
448 void *
chan_get_userdata(chan * ch)449 chan_get_userdata (chan *ch)
450 {
451 	return ch->userdata;
452 }
453 
454 void
chan_focus(chan * ch)455 chan_focus (chan *ch)
456 {
457 	if (ch->cv->focused == ch)
458 		return;
459 
460 	ch->cv->func_focus (ch);
461 }
462 
463 void
chan_move(chan * ch,int delta)464 chan_move (chan *ch, int delta)
465 {
466 	ch->cv->func_move (ch, delta);
467 }
468 
469 void
chan_move_family(chan * ch,int delta)470 chan_move_family (chan *ch, int delta)
471 {
472 	ch->cv->func_move_family (ch, delta);
473 }
474 
475 void
chan_set_color(chan * ch,PangoAttrList * list)476 chan_set_color (chan *ch, PangoAttrList *list)
477 {
478 	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_ATTR, list, -1);
479 	ch->cv->func_set_color (ch, list);
480 }
481 
482 void
chan_rename(chan * ch,char * name,int trunc_len)483 chan_rename (chan *ch, char *name, int trunc_len)
484 {
485 	char *new_name;
486 
487 	new_name = truncate_tab_name (name, trunc_len);
488 
489 	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_NAME, new_name, -1);
490 	ch->cv->func_rename (ch, new_name);
491 	ch->cv->trunc_len = trunc_len;
492 
493 	if (new_name != name)
494 		g_free (new_name);
495 }
496 
497 /* this thing is overly complicated */
498 
499 static int
cv_find_number_of_chan(chanview * cv,chan * find_ch)500 cv_find_number_of_chan (chanview *cv, chan *find_ch)
501 {
502 	GtkTreeIter iter, inner;
503 	chan *ch;
504 	int i = 0;
505 
506 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
507 	{
508 		do
509 		{
510 			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
511 			if (ch == find_ch)
512 				return i;
513 			i++;
514 
515 			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
516 			{
517 				do
518 				{
519 					gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
520 					if (ch == find_ch)
521 						return i;
522 					i++;
523 				}
524 				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
525 			}
526 		}
527 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
528 	}
529 
530 	return 0;	/* WARNING */
531 }
532 
533 /* this thing is overly complicated too */
534 
535 static chan *
cv_find_chan_by_number(chanview * cv,int num)536 cv_find_chan_by_number (chanview *cv, int num)
537 {
538 	GtkTreeIter iter, inner;
539 	chan *ch;
540 	int i = 0;
541 
542 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
543 	{
544 		do
545 		{
546 			if (i == num)
547 			{
548 				gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
549 				return ch;
550 			}
551 			i++;
552 
553 			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
554 			{
555 				do
556 				{
557 					if (i == num)
558 					{
559 						gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
560 						return ch;
561 					}
562 					i++;
563 				}
564 				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
565 			}
566 		}
567 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
568 	}
569 
570 	return NULL;
571 }
572 
573 static void
chan_emancipate_children(chan * ch)574 chan_emancipate_children (chan *ch)
575 {
576 	char *name;
577 	chan *childch;
578 	GtkTreeIter childiter;
579 	PangoAttrList *attr;
580 
581 	while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch->cv->store), &childiter, &ch->iter))
582 	{
583 		/* remove and re-add all the children, but avoid using "ch" as parent */
584 		gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &childiter,
585 								  COL_NAME, &name, COL_CHAN, &childch, COL_ATTR, &attr, -1);
586 		ch->cv->func_remove (childch);
587 		gtk_tree_store_remove (ch->cv->store, &childiter);
588 		ch->cv->size--;
589 		chanview_add_real (childch->cv, name, childch->family, childch->userdata, childch->allow_closure, childch->tag, childch->icon, childch, ch);
590 		if (attr)
591 		{
592 			childch->cv->func_set_color (childch, attr);
593 			pango_attr_list_unref (attr);
594 		}
595 		g_free (name);
596 	}
597 }
598 
599 gboolean
chan_remove(chan * ch,gboolean force)600 chan_remove (chan *ch, gboolean force)
601 {
602 	chan *new_ch;
603 	int i, num;
604 	extern int hexchat_is_quitting;
605 
606 	if (hexchat_is_quitting)	/* avoid lots of looping on exit */
607 		return TRUE;
608 
609 	/* is this ch allowed to be closed while still having children? */
610 	if (!force &&
611 		 gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch->cv->store), &ch->iter) &&
612 		 !ch->allow_closure)
613 		return FALSE;
614 
615 	chan_emancipate_children (ch);
616 	ch->cv->func_remove (ch);
617 
618 	/* is it the focused one? */
619 	if (ch->cv->focused == ch)
620 	{
621 		ch->cv->focused = NULL;
622 
623 		/* try to move the focus to some other valid channel */
624 		num = cv_find_number_of_chan (ch->cv, ch);
625 		/* move to the one left of the closing tab */
626 		new_ch = cv_find_chan_by_number (ch->cv, num - 1);
627 		if (new_ch && new_ch != ch)
628 		{
629 			chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
630 		} else
631 		{
632 			/* if it fails, try focus from tab 0 and up */
633 			for (i = 0; i < ch->cv->size; i++)
634 			{
635 				new_ch = cv_find_chan_by_number (ch->cv, i);
636 				if (new_ch && new_ch != ch)
637 				{
638 					chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
639 					break;
640 				}
641 			}
642 		}
643 	}
644 
645 	ch->cv->size--;
646 	gtk_tree_store_remove (ch->cv->store, &ch->iter);
647 	g_free (ch);
648 	return TRUE;
649 }
650 
651 gboolean
chan_is_collapsed(chan * ch)652 chan_is_collapsed (chan *ch)
653 {
654 	return ch->cv->func_is_collapsed (ch);
655 }
656 
657 chan *
chan_get_parent(chan * ch)658 chan_get_parent (chan *ch)
659 {
660 	return ch->cv->func_get_parent (ch);
661 }
662