1 /*
2  * ui_draw.c -- drawing of a nodes and trees using curses
3  *
4  * Copyright (C) 2001-2003 �yvind Kol�s <pippin@users.sourceforge.net>
5  *
6  * This program is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License as published by the Free
8  * Software Foundation; either version 2, or (at your option) any later
9  * version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * this program; if not, write to the Free Software Foundation, Inc., 59
18  * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #if HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <assert.h>
25 #include "tree.h"
26 #include "tree_todo.h"
27 #include <string.h>
28 #include <unistd.h>
29 #include "curses.h"
30 #include "stdio.h"
31 #include "prefs.h"
32 #include "ui_overlay.h"
33 #define UI_C
34 #include "ui.h"
35 #include "ui_draw.h"
36 #include "cli.h"
37 #include <stdlib.h>
38 #include <ctype.h>
39 
40 #define KEEPLINES 5
41 
42 static int nodes_above;
43 static int active_line;
44 static int nodes_below;
45 
up(Node * sel,Node * node)46 static Node *up (Node *sel, Node *node)
47 {
48 	if (node_up (node) && node_getflag( node_up (node), F_expanded)) {
49 		node = node_up (node);
50 		while (node_right (node) && node_getflag(node,F_expanded)) {
51 			node = node_right (node);
52 			node = node_bottom (node);
53 		}
54 		return (node);
55 	} else {
56 		if (node_up (node))
57 			return (node_up (node));
58 		else
59 			return (node_left (node));
60 	}
61 	return node_left (node);
62 }
63 
down(Node * sel,Node * node)64 static Node *down (Node *sel, Node *node)
65 {
66 	if (node_getflag(node,F_expanded)) {
67 		return node_recurse (node);
68 	} else {
69 		if (node_down (node)) {
70 			return (node_down (node));
71 		} else {
72 			while (node != 0) {
73 				node = node_left (node);
74 				if (node_down (node))
75 					return (node_down (node));
76 			}
77 		}
78 	}
79 	return NULL;
80 }
81 
82 int startlevel = 0;
83 
84 int hnb_edit_posup = 0;			/*contains the cursor pos for up/down */
85 int hnb_edit_posdown = 0;		/*from here when in editing mode */
86 
87 enum {
88 	drawmode_test = 0,
89 	drawmode_normal,
90 	drawmode_selected,
91 	drawmode_edit,
92 	drawmode_completion
93 };
94 
95 /* draws the actual node data with word wrapping, should be reengineered into a general
96  * linewrapping function.
97  *
98  *
99  */
draw_textblock(int line_start,int col_start,int width,int cursor_pos,Node * node,int drawmode)100 static int draw_textblock (int line_start, int col_start, int width,
101 						   int cursor_pos, Node *node, int drawmode)
102 {
103 	int col_end = col_start + width;
104 	unsigned char word[200];	/* current word being rendered */
105 	int wpos = 0;				/* position in current word */
106 	int dpos = 0;				/* position in data */
107 	int col;					/* current column */
108 
109 	int lines_used = 1;
110 
111 	int cursor_state = 0;
112 	int cx = 0, cy = 0;			/* coordinates to draw cursor at */
113 
114 	unsigned char *data =
115 		(unsigned char *) fixnullstring (node_get (node, TEXT));
116 
117 	col = col_start;
118 
119 	word[0] = 0;
120 	if (drawmode == drawmode_edit) {
121 		hnb_edit_posup = 0;
122 		hnb_edit_posdown = strlen ((char *) data);
123 	}
124 
125 	switch (drawmode) {
126 		case drawmode_test:
127 			break;
128 		case drawmode_completion:
129 			if (node_right (node)) {
130 				ui_style (ui_style_parentnode);
131 			} else {
132 				ui_style (ui_style_node);
133 			}
134 			break;
135 		case drawmode_selected:
136 			if (node_right (node)) {
137 				ui_style (ui_style_parentselected);
138 			} else {
139 				ui_style (ui_style_selected);
140 			}
141 			break;
142 		case drawmode_normal:
143 		case drawmode_edit:
144 		default:
145 			if (node_right (node)) {
146 				ui_style (ui_style_parentnode);
147 			} else {
148 				ui_style (ui_style_node);
149 			}
150 			break;
151 	}
152 
153 	while ((dpos == 0) || data[dpos - 1]) {	/* loop through data + \0 */
154 		switch (data[dpos]) {
155 			case '\0':			/* \0 as well,.. to print last word */
156 			case ' ':
157 			case '\t':
158 			case '\n':
159 			case '\r':			/* all whitespace is treated as spaces */
160 				if (col + wpos + 1 >= col_end) {	/* reached margin */
161 					if (drawmode == drawmode_edit) {
162 						if (cursor_state == 0)
163 							hnb_edit_posup = cursor_pos - (col - col_start);
164 						if (cursor_state == 1) {
165 							hnb_edit_posdown = cursor_pos + (col - col_start);
166 							cursor_state = 2;
167 						}
168 					}
169 
170 					col = col_start;
171 					lines_used++;
172 					if (lines_used + line_start >= LINES)
173 						return lines_used;
174 				}
175 				if (drawmode != drawmode_test) {
176 					if (line_start + lines_used - 1 >= 0) {
177 						move (line_start + lines_used - 1, col);
178 
179 						/* must break the word in two due to differnt text formatting */
180 						if (drawmode == drawmode_completion
181 							&& cursor_state == 0 && dpos >= cursor_pos) {
182 							int i;
183 
184 							for (i = 0; i < wpos - (dpos - cursor_pos); i++)
185 								addch (word[i]);
186 							if (node_right (node)) {
187 								ui_style (ui_style_parentselected);
188 							} else {
189 								ui_style (ui_style_selected);
190 							}
191 							for (i = wpos - (dpos - cursor_pos); i < wpos;
192 								 i++)
193 								addch (word[i]);
194 						} else {
195 
196 							addstr ((char *) word);
197 
198 						}
199 						if (data[dpos])
200 							addch (' ');
201 					}
202 				}
203 
204 				switch (drawmode) {
205 					case drawmode_edit:
206 						if (cursor_state == 0 && dpos >= cursor_pos) {
207 							cy = line_start + lines_used - 1;
208 							cx = col - (dpos - cursor_pos) + wpos;
209 							cursor_state = 1;
210 						}
211 						break;
212 					case drawmode_completion:
213 						if (cursor_state == 0 && dpos >= cursor_pos) {
214 							if (node_right (node)) {
215 								ui_style (ui_style_parentselected);
216 							} else {
217 								ui_style (ui_style_selected);
218 							}
219 							cursor_state = 1;
220 						}
221 					default:
222 						break;
223 				}
224 
225 				col += wpos + 1;
226 				word[wpos = 0] = 0;
227 				break;
228 			default:
229 				if (wpos < 198) {
230 					word[wpos++] = data[dpos];
231 					word[wpos] = 0;
232 				}
233 				break;
234 		}
235 		dpos++;
236 	}
237 	/* draw the cursor */
238 	if (drawmode == drawmode_edit) {
239 		move (cy, cx);
240 		if (node_right (node)) {
241 			ui_style (ui_style_parentselected);
242 		} else {
243 			ui_style (ui_style_selected);
244 		}
245 		addch (data[cursor_pos]);
246 		if (node_right (node)) {
247 			ui_style (ui_style_parentnode);
248 		} else {
249 			ui_style (ui_style_node);
250 		}
251 	}
252 	return lines_used;
253 }
254 
255 
256 
draw_dummy(int line,int col,int width,Node * node,int drawmode)257 static int draw_dummy (int line, int col, int width, Node *node, int drawmode)
258 {
259 	if (width == 0)
260 		width = 1;
261 	if (drawmode != drawmode_test) {
262 		int j;
263 
264 		move (line, col);
265 		ui_style (ui_style_bullet);
266 		for (j = 0; j < width; j++) {
267 			addch ('X');
268 		}
269 	}
270 	return width;
271 }
272 
draw_spacing(int line,int col,int width,Node * node,int drawmode)273 static int draw_spacing (int line, int col, int width, Node *node,
274 						 int drawmode)
275 {
276 	if (width == 0)
277 		width = 1;
278 	if (drawmode != drawmode_test) {
279 		int j;
280 
281 		move (line, col);
282 		ui_style (ui_style_background);
283 		for (j = 0; j < width; j++) {
284 			addch (' ');
285 		}
286 	}
287 	return width;
288 }
289 
290 static char bullet_leaf[4] = "  �";
291 static char bullet_parent[4] = "  +";
292 static char bullet_parent_expanded[4] = "  -";
293 
294 
295 
draw_bullet(int line,int col,int width,Node * node,int drawmode)296 static int draw_bullet (int line, int col, int width, Node *node,
297 						int drawmode)
298 {
299 	int asize;
300 	int perc;
301 
302 /*	if(width==0)*/
303 	width = 3;
304 
305 	perc = calc_percentage_size (node, &asize);
306 	{
307 		ui_style (ui_style_bullet);
308 
309 		move (line, col);
310 		switch (perc) {
311 			case -1:
312 				if (drawmode != drawmode_test)
313 					addstr ((node_right (node)) ? node_getflag(node,F_expanded)
314 							? bullet_parent_expanded : bullet_parent
315 							: bullet_leaf);
316 				break;
317 			case 0:
318 				if (drawmode != drawmode_test)
319 					addstr ("[ ]");
320 				break;
321 			case 2000:
322 				if (drawmode != drawmode_test)
323 					addstr ("[X]");
324 				break;
325 			default:{
326 				char str[100];
327 
328 				snprintf (str, 4, "%2i%%", perc);
329 				if (drawmode != drawmode_test)
330 					addstr (str);
331 			}
332 		}
333 	}
334 
335 	return width;
336 }
337 
338 
node2no_path(Node * node)339 static char *node2no_path (Node *node)
340 {
341 	static char path[512];
342 	int pos = 0;
343 	int levels = nodes_left (node);
344 	int cnt;
345 
346 	path[0] = 0;
347 
348 	for (cnt = levels; cnt >= 0; cnt--) {
349 		int cnt2;
350 		Node *tnode = node;
351 
352 		for (cnt2 = 0; cnt2 < cnt; cnt2++)
353 			tnode = node_left (tnode);
354 
355 		sprintf (&path[pos], "%i", nodes_up (tnode) + 1);
356 		pos = strlen (path);
357 		path[pos] = '.';
358 		path[++pos] = 0;
359 	}
360 
361 	path[--pos] = 0;
362 
363 	return (path);
364 }
365 
draw_node_no(int line,int col,int width,Node * node,int drawmode)366 static int draw_node_no (int line, int col, int width, Node *node,
367 						 int drawmode)
368 {
369 	char str[100] = "";
370 
371 	if (width == 0)
372 		width = 4;
373 
374 	ui_style (ui_style_bullet);
375 	move (line, col);
376 	snprintf (str, 5, "%4i", node_no (node));
377 	if (drawmode != drawmode_test) {
378 		addstr (str);
379 	}
380 
381 	return width;
382 }
383 
384 
draw_nr(int line,int col,int width,Node * node,int drawmode)385 static int draw_nr (int line, int col, int width, Node *node, int drawmode)
386 {
387 	char str[100] = "";
388 
389 	if (width == 0)
390 		width = 3;
391 
392 	ui_style (ui_style_bullet);
393 	move (line, col);
394 	snprintf (str, 5, "%3i", nodes_up (node) + 1);
395 	if (drawmode != drawmode_test) {
396 		addstr (str);
397 	}
398 
399 	return width;
400 }
401 
402 
403 
draw_anr(int line,int col,int width,Node * node,int drawmode)404 static int draw_anr (int line, int col, int width, Node *node, int drawmode)
405 {
406 	char str[100] = "";
407 	char fstr[20];
408 
409 	if (width == 0)
410 		width = 8;
411 
412 	ui_style (ui_style_bullet);
413 	move (line, col);
414 	snprintf (fstr, 8, "%%%is", width);
415 	snprintf (str, width + 2, fstr, node2no_path (node));
416 	if (drawmode != drawmode_test) {
417 		addstr (str);
418 	}
419 
420 	return width;
421 }
422 
node_getval(Node * node,char * name)423 static int node_getval(Node *node, char *name){
424 	char *got=node_get(node,name);
425 	if(!got)return -1;
426 	return(atoi(got));
427 }
428 
429 
draw_debug(int line,int col,int width,Node * node,int drawmode)430 static int draw_debug (int line, int col, int width, Node *node, int drawmode)
431 {
432 	int asize;
433 	int size = node_getval (node,"size");
434 	int perc;
435 
436 	width = 40;
437 
438 	if (drawmode != drawmode_test) {
439 		ui_style (ui_style_background);
440 		move (line, col);
441 		perc = calc_percentage_size (node, &asize);
442 
443 		{
444 			char str[64];
445 
446 			sprintf (str, "(%i/%i) ",
447 					 (perc == 2000 ? 100 : perc * asize) / 100, asize);
448 			if (drawmode != drawmode_test)
449 				addstr (str);
450 		}
451 
452 		{
453 			perc = calc_percentage_size (node, &asize);
454 			attrset (A_NORMAL);
455 			{
456 				char str[256];
457 
458 				sprintf (str, "size:%i a_size:%i %i%% ", node_getval (node,"size"),
459 						 size, perc);
460 				addstr (str);
461 			}
462 		}
463 
464 		if (node_calc_size (node) != -1) {
465 			char str[10];
466 
467 			sprintf (str, "%4.1f ", (float) node_calc_size (node) / 10.0);
468 			addstr (str);
469 		}
470 	}
471 
472 	return width;
473 }
474 
475 #define MAX_COLUMNS 20
476 
draw_indent(int line,int col,int width,Node * node,int drawmode)477 static int draw_indent (int line, int col, int width, Node *node,
478 						int drawmode)
479 {
480 	if (width == 0)
481 		width = 4;
482 
483 	return width * nodes_left (node);
484 }
485 
486 
487 enum {
488 	col_spacing = 0,
489 	col_indent,
490 	col_nr,
491 	col_anr,
492 	col_bullet,
493 	col_data,
494 	col_debug,
495 	col_percentage,
496 	col_node_no,
497 	col_dummy,
498 	col_terminate
499 };
500 
501 
502 static int (*col_fun[col_terminate + 1]) (int line, int col, int width,
503 										  Node *node, int drawmode) = {
504 draw_spacing, draw_indent, draw_nr, draw_anr, draw_bullet, draw_spacing,
505 		draw_debug, draw_spacing, draw_node_no, draw_dummy, draw_dummy};
506 
507 static struct {
508 	int type;
509 	int width;
510 } col_def[MAX_COLUMNS] = {
511 	{
512 	col_indent, 4}, {
513 	col_spacing, 1}, {
514 	col_bullet, 3}, {
515 	col_spacing, 1}, {
516 	col_data, 0}, {
517 	col_spacing, 1}, {
518 	col_dummy, 10}, {
519 	col_spacing, 2}, {
520 	col_dummy, 10}, {
521 	col_spacing, 1}, {
522 	col_terminate, 0}
523 };
524 
525 
526 /* FIXME: make backup?,.. and make sure data is present,.., make possiblity to write back? */
527 
display_format_cmd(int argc,char ** argv,void * data)528 uint64_t display_format_cmd (int argc, char **argv, void *data)
529 {
530 	char *p = argv[1];
531 	int width;
532 	int type;
533 	int col_no = 0;
534 
535 	if(argc<2){
536 		return PTR_TO_UINT64(data);
537 	}
538 
539 	do {
540 		width = 0;
541 		type = col_spacing;
542 		switch (*p) {
543 			case 'i':
544 				type = col_indent;
545 				if (isdigit (*(p + 1))) {
546 					width = atoi (p + 1);
547 					while (isdigit ((unsigned char)*(p + 1)))
548 						p++;
549 				}
550 				break;
551 			case 'd':
552 				type = col_data;
553 				if (isdigit (*(p + 1))) {
554 					width = atoi (p + 1);
555 					while (isdigit ((unsigned char)*(p + 1)))
556 						p++;
557 				}
558 				break;
559 			case 'D':
560 				type = col_debug;
561 				if (isdigit (*(p + 1))) {
562 					width = atoi (p + 1);
563 					while (isdigit ((unsigned char)*(p + 1)))
564 						p++;
565 				}
566 				break;
567 			case 'x':
568 				type = col_dummy;
569 				if (isdigit (*(p + 1))) {
570 					width = atoi (p + 1);
571 					while (isdigit ((unsigned char)*(p + 1)))
572 						p++;
573 				}
574 				break;
575 			case '1':
576 				type = col_nr;
577 				if (*(p + 1) == '.') {
578 					type = col_anr;
579 					p++;
580 				}
581 				if (isdigit (*(p + 1))) {
582 					width = atoi (p + 1);
583 					while (isdigit ((unsigned char)*(p + 1)))
584 						p++;
585 				}
586 				break;
587 			case '-':
588 				type = col_bullet;
589 				if (isdigit (*(p + 1))) {
590 					width = atoi (p + 1);
591 					while (isdigit ((unsigned char)*(p + 1)))
592 						p++;
593 				}
594 				break;
595 			case '#':
596 				type = col_node_no;
597 				if (isdigit (*(p + 1))) {
598 					width = atoi (p + 1);
599 					while (isdigit ((unsigned char)*(p + 1)))
600 						p++;
601 				}
602 				break;
603 			case ' ':
604 				type = col_spacing;
605 				while (' ' == ((unsigned char)*(p + 1))) {
606 					p++;
607 					width++;
608 				}
609 				break;
610 			default:
611 				cli_outfunf ("td not_parsed(%c)", *p);
612 				break;
613 		}
614 		col_def[col_no].type = type;
615 		col_def[col_no].width = width;
616 		col_no++;
617 	} while (*(++p));
618 
619 	col_def[col_no].type = col_terminate;
620 
621 	return PTR_TO_UINT64(data);
622 }
623 
624 
625 
626 
627 
628 /*
629  * @param line_start which line on the display the first line of the draw node is on
630  * @param level      the indentation level of this item
631  * @param node       the node to draw
632  * @param cursor_pos different meanings in different modes, testmode: none
633  *                   highlightmode: none, edit_mode: the position in the data
634  *                   that should be highlighted,
635  *                   completion: the number of matched chars in data
636  *
637  * @param draw_mode  1=draw, 0=test
638  *
639  * @return number of lines needed to draw item
640  **/
draw_item(int line_start,int cursor_pos,Node * node,int drawmode)641 static int draw_item (int line_start, int cursor_pos, Node *node,
642 					  int drawmode)
643 {
644 	int col_no = 0;
645 	int lines_used = 1;
646 
647 	int col_start = 0;
648 	int col_end = COLS;
649 
650 	col_start = 0;
651 	/* draw columns before col_data */
652 
653 	while (col_def[col_no].type != col_data
654 		   && col_def[col_no].type != col_terminate) {
655 		col_start +=
656 			col_fun[col_def[col_no].type] (line_start, col_start,
657 										   col_def[col_no].width, node,
658 										   drawmode);
659 		col_no++;
660 	}
661 
662 	/* fastforward to end of col_def */
663 	while (col_def[col_no].type != col_terminate)
664 		col_no++;
665 
666 	col_no--;
667 
668 	/* draw columns after col_data */
669 	while (col_no && col_def[col_no].type != col_data) {
670 		int width =
671 			col_fun[col_def[col_no].type] (line_start,
672 										   col_end - col_def[col_no].width,
673 										   col_def[col_no].width, node,
674 										   drawmode_test);
675 		col_end -=
676 			col_fun[col_def[col_no].type] (line_start, col_end - width, width,
677 										   node, drawmode);
678 		col_no--;
679 	}
680 
681 	lines_used =
682 		draw_textblock (line_start, col_start, (col_end - col_start),
683 						cursor_pos, node, drawmode);
684 
685 	return lines_used;
686 }
687 
688 extern int hnb_nodes_up;
689 extern int hnb_nodes_down;
690 
691 #define MAXLINES 512
692 static int line_nodeno[MAXLINES] = { 0 };
693 
ui_draw(Node * node,char * input,int edit_mode)694 void ui_draw (Node *node, char *input, int edit_mode)
695 {
696 	int lines;
697 
698 	static struct {
699 		int self;
700 		int prev;
701 	} node_numb = {
702 	1, 1};
703 
704 	if (!prefs.fixedfocus) {
705 
706 		node_numb.prev = node_numb.self;
707 		node_numb.self = node_no (node);
708 
709 		if (node_numb.self > node_numb.prev) {
710 			active_line++;
711 		} else if (node_numb.self < node_numb.prev) {
712 			active_line--;
713 		}
714 
715 		{
716 			int i;
717 
718 			for (i = 0; i < ((LINES < MAXLINES) ? LINES : MAXLINES); i++)
719 				if (line_nodeno[i] == node_numb.self) {
720 					active_line = i;
721 					break;
722 				}
723 		}
724 
725 		if (node_numb.self == 1) {	/* jumped to root, always bring nodes to top of screen */
726 			active_line = 1;
727 		}
728 
729 		{
730 			int i;
731 
732 			for (i = 0; i < ((LINES < MAXLINES) ? LINES : MAXLINES); i++)
733 				line_nodeno[i] = 0;
734 		}
735 
736 
737 		{
738 			int maxline = LINES - KEEPLINES;
739 
740 			if (active_line > maxline)	/*if we overlap with help,.. move up */
741 				active_line = maxline;
742 			if (active_line < KEEPLINES)
743 				active_line = KEEPLINES;
744 		}
745 	};
746 
747 	nodes_above = active_line;
748 	nodes_below = LINES - active_line;
749 
750 	{
751 		hnb_nodes_up = 0;
752 		hnb_nodes_down = 0;
753 
754 		erase ();
755 /* draw nodes above selected node */
756 		{
757 			Node *prev_down = node;	/* to aid pgup/pgdn */
758 			int line = active_line;
759 			Node *tnode = up (node, node);
760 
761 			while (tnode) {
762 				draw_item (line -=
763 						   draw_item (0, 0, tnode, drawmode_test), 0, tnode,
764 						   drawmode_normal);
765 
766 				line_nodeno[line] = node_no (tnode);
767 
768 				if (node_down (tnode) == prev_down) {
769 					hnb_nodes_up++;
770 					prev_down = tnode;
771 				}
772 
773 				tnode = up (node, tnode);
774 				if (active_line - nodes_above >= line)
775 					tnode = 0;
776 			}
777 		}
778 /* draw the currently selected item */
779 
780 		line_nodeno[active_line] = node_no (node);
781 
782 		if (edit_mode) {
783 			lines = draw_item (active_line, (int) input, node, drawmode_edit);
784 		} else {
785 			lines =
786 				draw_item (active_line, strlen (input), node,
787 						   drawmode_completion);
788 		}
789 
790 /* draw items below current item */
791 		{
792 			Node *prev_up = node;	/* to aid pgup/pgdn */
793 			Node *tnode = down (node, node);
794 
795 			lines += active_line;
796 			if (lines >= LINES)
797 				tnode = 0;
798 			while (tnode) {
799 				line_nodeno[lines] = node_no (tnode);
800 				lines += draw_item (lines, 0, tnode, drawmode_normal);
801 
802 				if (node_up (tnode) == prev_up) {
803 					hnb_nodes_down++;
804 					prev_up = tnode;
805 				}
806 
807 				tnode = down (node, tnode);
808 				if (lines >= LINES)
809 					tnode = 0;
810 
811 			}
812 		}
813 	}
814 
815 	help_draw (ui_current_scope);
816 
817 	move (LINES - 1, COLS - 1);
818 
819 /*	refresh ();*/
820 
821 	hnb_nodes_up++;
822 	hnb_nodes_down++;
823 }
824 
825 /*
826 !init_ui_draw();
827 */
init_ui_draw()828 void init_ui_draw ()
829 {
830 	cli_add_command ("display_format", display_format_cmd, "<format string>");
831 	cli_add_help ("display_format", "\
832 defines how each node is displayed, the display string syntax is \
833 interpreted as follows: \
834 spaces turn into real spaces, i means indentation, - means bullet, \
835 d means the real data of the node, x is a temporary placeholder for \
836 upcoming columntypes,. (for debugging only) \
837 i and x can take an argument specifying how many characters wide \
838 the field should be");
839 	cli_add_string ("bullet_leaf", bullet_leaf, "");
840 	cli_add_string ("bullet_parent", bullet_parent, "");
841 	cli_add_string ("bullet_parent_expanded", bullet_parent_expanded, "");
842 
843 }
844