]> git.ipfire.org Git - thirdparty/newt.git/blob - checkboxtree.c
- make textbox with scrollbar focusable (#83203)
[thirdparty/newt.git] / checkboxtree.c
1 #include <slang.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #include "newt.h"
6 #include "newt_pr.h"
7
8 struct items {
9 char * text;
10 const void *data;
11 unsigned char selected;
12 struct items *next;
13 struct items *prev;
14 struct items *branch;
15 int flags;
16 int depth;
17 };
18
19 struct CheckboxTree {
20 newtComponent sb;
21 struct items * itemlist;
22 struct items ** flatList, ** currItem, ** firstItem;
23 int flatCount;
24 int flags;
25 int sbAdjust;
26 int curWidth;
27 int userHasSetWidth;
28 int isActive;
29 char * seq;
30 char * result;
31 };
32
33 static void ctDraw(newtComponent c);
34 static void ctDestroy(newtComponent co);
35 static void ctPlace(newtComponent co, int newLeft, int newTop);
36 struct eventResult ctEvent(newtComponent co, struct event ev);
37 static void ctMapped(newtComponent co, int isMapped);
38 static struct items * findItem(struct items * items, const void * data);
39 static void buildFlatList(newtComponent co);
40 static void doBuildFlatList(struct CheckboxTree * ct, struct items * item);
41 enum countWhat { COUNT_EXPOSED=0, COUNT_SELECTED=1 };
42 static int countItems(struct items * item, enum countWhat justExposed);
43 static inline void updateWidth(newtComponent co, struct CheckboxTree * ct,
44 int maxField);
45
46 static struct componentOps ctOps = {
47 ctDraw,
48 ctEvent,
49 ctDestroy,
50 ctPlace,
51 ctMapped,
52 } ;
53
54 static inline void updateWidth(newtComponent co, struct CheckboxTree * ct,
55 int maxField) {
56 ct->curWidth = maxField;
57 co->width = ct->curWidth + ct->sbAdjust;
58
59 if (ct->sb)
60 ct->sb->left = co->left + co->width - 1;
61 }
62
63 static int countItems(struct items * item, enum countWhat what) {
64 int count = 0;
65
66 while (item) {
67 if ((!item->branch && item->selected == what) || (what == COUNT_EXPOSED))
68 count++;
69 if (item->branch || (what == COUNT_EXPOSED && item->selected))
70 count += countItems(item->branch, what);
71 item = item->next;
72 }
73
74 return count;
75 }
76
77 static void doBuildFlatList(struct CheckboxTree * ct, struct items * item) {
78 while (item) {
79 ct->flatList[ct->flatCount++] = item;
80 if (item->branch && item->selected) doBuildFlatList(ct, item->branch);
81 item = item->next;
82 }
83 }
84
85 /* FIXME: Check what happens on malloc failure.
86 */
87 static void buildFlatList(newtComponent co) {
88 struct CheckboxTree * ct = co->data;
89
90 if (ct->flatList) free(ct->flatList);
91 ct->flatCount = countItems(ct->itemlist, COUNT_EXPOSED);
92
93 ct->flatList = malloc(sizeof(*ct->flatList) * (ct->flatCount+1));
94 ct->flatCount = 0;
95 doBuildFlatList(ct, ct->itemlist);
96 ct->flatList[ct->flatCount] = NULL;
97 }
98
99 int newtCheckboxTreeAddItem(newtComponent co,
100 const char * text, const void * data,
101 int flags, int index, ...) {
102 va_list argList;
103 int numIndexes;
104 int * indexes;
105 int i;
106
107 va_start(argList, index);
108 numIndexes = 0;
109 i = index;
110 while (i != NEWT_ARG_LAST) {
111 numIndexes++;
112 i = va_arg(argList, int);
113 }
114
115 va_end(argList);
116
117 indexes = alloca(sizeof(*indexes) * (numIndexes + 1));
118 va_start(argList, index);
119 numIndexes = 0;
120 i = index;
121 va_start(argList, index);
122 while (i != NEWT_ARG_LAST) {
123 indexes[numIndexes++] = i;
124 i = va_arg(argList, int);
125 }
126 va_end(argList);
127
128 indexes[numIndexes++] = NEWT_ARG_LAST;
129
130 return newtCheckboxTreeAddArray(co, text, data, flags, indexes);
131 }
132
133 static int doFindItemPath(struct items * items, void * data, int * path,
134 int * len) {
135 int where = 0;
136
137 while (items) {
138 if (items->data == data) {
139 if (path) path[items->depth] = where;
140 if (len) *len = items->depth + 1;
141 return 1;
142 }
143
144 if (items->branch && doFindItemPath(items->branch, data, path, len)) {
145 if (path) path[items->depth] = where;
146 return 1;
147 }
148
149 items = items->next;
150 where++;
151 }
152
153 return 0;
154 }
155
156 int * newtCheckboxTreeFindItem(newtComponent co, void * data) {
157 int len;
158 int * path;
159 struct CheckboxTree * ct = co->data;
160
161 if (!doFindItemPath(ct->itemlist, data, NULL, &len)) return NULL;
162
163 path = malloc(sizeof(*path) * (len + 1));
164 doFindItemPath(ct->itemlist, data, path, NULL);
165 path[len] = NEWT_ARG_LAST;
166
167 return path;
168 }
169
170 int newtCheckboxTreeAddArray(newtComponent co,
171 const char * text, const void * data,
172 int flags, int * indexes) {
173 struct items * curList, * newNode, * item = NULL;
174 struct items ** listPtr = NULL;
175 int i, index, numIndexes, width;
176 struct CheckboxTree * ct = co->data;
177
178 numIndexes = 0;
179 while (indexes[numIndexes] != NEWT_ARG_LAST) numIndexes++;
180
181 if (!ct->itemlist) {
182 if (numIndexes > 1) return -1;
183
184 ct->itemlist = malloc(sizeof(*ct->itemlist)); // FIXME: Error check?
185 item = ct->itemlist;
186 item->prev = NULL;
187 item->next = NULL;
188 } else {
189 curList = ct->itemlist;
190 listPtr = &ct->itemlist;
191
192 i = 0;
193 index = indexes[i];
194 while (i < numIndexes) {
195 item = curList;
196
197 if (index == NEWT_ARG_APPEND) {
198 item = NULL;
199 } else {
200 while (index && item)
201 item = item->next, index--;
202 }
203
204 i++;
205 if (i < numIndexes) {
206 curList = item->branch;
207 listPtr = &item->branch;
208 if (!curList && (i + 1 != numIndexes)) return -1;
209
210 index = indexes[i];
211 }
212 }
213
214 if (!curList) { /* create a new branch */
215 item = malloc(sizeof(*curList->prev));
216 item->next = item->prev = NULL;
217 *listPtr = item;
218 } else if (!item) { /* append to end */
219 item = curList;
220 while (item->next) item = item->next;
221 item->next = malloc(sizeof(*curList->prev)); // FIXME Error check
222 item->next->prev = item;
223 item = item->next;
224 item->next = NULL;
225 } else {
226 newNode = malloc(sizeof(*newNode)); // FIXME Error check ?
227 newNode->prev = item->prev;
228 newNode->next = item;
229
230 if (item->prev) item->prev->next = newNode;
231 item->prev = newNode;
232 item = newNode;
233 if (!item->prev) *listPtr = item;
234 }
235 }
236
237 item->text = strdup(text);
238 item->data = data;
239 if (flags & NEWT_FLAG_SELECTED) {
240 item->selected = 1;
241 } else {
242 item->selected = 0;
243 }
244 item->flags = flags;
245 item->branch = NULL;
246 item->depth = numIndexes - 1;
247
248 i = 4 + (3 * item->depth);
249 width = wstrlen(text, -1);
250
251 if ((ct->userHasSetWidth == 0) && ((width + i + ct->sbAdjust) > co->width)) {
252 updateWidth(co, ct, width + i);
253 }
254
255 return 0;
256 }
257
258 static struct items * findItem(struct items * items, const void * data) {
259 struct items * i;
260
261 while (items) {
262 if (items->data == data) return items;
263 if (items->branch) {
264 i = findItem(items->branch, data);
265 if (i) return i;
266 }
267
268 items = items->next;
269 }
270
271 return NULL;
272 }
273
274 static void listSelected(struct items * items, int * num, const void ** list, int seqindex) {
275 while (items) {
276 if ((seqindex ? items->selected==seqindex : items->selected) && !items->branch)
277 list[(*num)++] = (void *) items->data;
278 if (items->branch)
279 listSelected(items->branch, num, list, seqindex);
280 items = items->next;
281 }
282 }
283
284 void newtCheckboxTreeSetWidth(newtComponent co, int width) {
285 struct CheckboxTree * ct = co->data;
286
287 co->width = width;
288 ct->curWidth = co->width - ct->sbAdjust;
289 ct->userHasSetWidth = 1;
290 if (ct->sb) ct->sb->left = co->width + co->left - 1;
291 ctDraw(co);
292 }
293
294 const void ** newtCheckboxTreeGetSelection(newtComponent co, int *numitems)
295 {
296 return newtCheckboxTreeGetMultiSelection(co, numitems, 0);
297 }
298
299 const void ** newtCheckboxTreeGetMultiSelection(newtComponent co, int *numitems, char seqnum)
300 {
301 struct CheckboxTree * ct;
302 const void **retval;
303 int seqindex=0;
304
305 if(!co || !numitems) return NULL;
306
307 ct = co->data;
308
309 if (seqnum) {
310 while( ct->seq[seqindex] && ( ct->seq[seqindex] != seqnum )) seqindex++;
311 } else {
312 seqindex = 0;
313 }
314
315 *numitems = countItems(ct->itemlist, (seqindex ? seqindex : COUNT_SELECTED));
316 if (!*numitems) return NULL;
317
318 retval = malloc(*numitems * sizeof(void *));
319 *numitems = 0;
320 listSelected(ct->itemlist, numitems, retval, seqindex);
321
322 return retval;
323 }
324
325 newtComponent newtCheckboxTree(int left, int top, int height, int flags) {
326 return newtCheckboxTreeMulti(left, top, height, NULL, flags);
327 }
328
329 newtComponent newtCheckboxTreeMulti(int left, int top, int height, char *seq, int flags) {
330 newtComponent co;
331 struct CheckboxTree * ct;
332
333 co = malloc(sizeof(*co));
334 ct = malloc(sizeof(struct CheckboxTree));
335 co->callback = NULL;
336 co->data = ct;
337 co->left = left;
338 co->top = top;
339 co->ops = &ctOps;
340 co->takesFocus = 1;
341 co->height = height;
342 co->width = 0;
343 co->isMapped = 0;
344 ct->curWidth = 0;
345 ct->isActive = 0;
346 ct->userHasSetWidth = 0;
347 ct->itemlist = NULL;
348 ct->firstItem = NULL;
349 ct->currItem = NULL;
350 ct->flatList = NULL;
351
352 ct->flags = flags;
353
354 if (seq)
355 ct->seq = strdup(seq);
356 else
357 ct->seq = strdup(" *");
358 if (flags & NEWT_FLAG_SCROLL) {
359 ct->sb = newtVerticalScrollbar(left, top, height,
360 COLORSET_LISTBOX, COLORSET_ACTLISTBOX);
361 ct->sbAdjust = 2;
362 } else {
363 ct->sb = NULL;
364 ct->sbAdjust = 0;
365 }
366
367 return co;
368 }
369
370 static void ctMapped(newtComponent co, int isMapped) {
371 struct CheckboxTree * ct = co->data;
372
373 co->isMapped = isMapped;
374 if (ct->sb)
375 ct->sb->ops->mapped(ct->sb, isMapped);
376 }
377
378 static void ctPlace(newtComponent co, int newLeft, int newTop) {
379 struct CheckboxTree * ct = co->data;
380
381 co->top = newTop;
382 co->left = newLeft;
383
384 if (ct->sb)
385 ct->sb->ops->place(ct->sb, co->left + co->width - 1, co->top);
386 }
387
388 int ctSetItem(newtComponent co, struct items *item, enum newtFlagsSense sense)
389 {
390 struct CheckboxTree * ct = co->data;
391 struct items * currItem;
392 struct items * firstItem;
393
394 if (!item)
395 return 1;
396
397 switch(sense) {
398 case NEWT_FLAGS_RESET:
399 item->selected = 0;
400 break;
401 case NEWT_FLAGS_SET:
402 item->selected = 1;
403 break;
404 case NEWT_FLAGS_TOGGLE:
405 if (item->branch)
406 item->selected = !item->selected;
407 else if (!(ct->flags & NEWT_CHECKBOXTREE_UNSELECTABLE)) {
408 item->selected++;
409 if (item->selected==strlen(ct->seq))
410 item->selected = 0;
411 }
412 break;
413 }
414
415 if (item->branch) {
416 currItem = *ct->currItem;
417 firstItem = *ct->firstItem;
418
419 buildFlatList(co);
420
421 ct->currItem = ct->flatList;
422 while (*ct->currItem != currItem) ct->currItem++;
423
424 ct->firstItem = ct->flatList;
425 if (ct->flatCount > co->height) {
426 struct items ** last = ct->flatList + ct->flatCount - co->height;
427 while (*ct->firstItem != firstItem && ct->firstItem != last)
428 ct->firstItem++;
429 }
430 }
431
432 return 0;
433 }
434
435 static void ctSetItems(struct items *item, int selected)
436 {
437 for (; item; item = item->next) {
438 if (!item->branch)
439 item->selected = selected;
440 else
441 ctSetItems(item->branch, selected);
442 }
443 }
444
445 static void ctDraw(newtComponent co) {
446 struct CheckboxTree * ct = co->data;
447 struct items ** item;
448 int i, j;
449 char * spaces;
450 int currRow;
451
452 if (!co->isMapped) return ;
453
454 if (!ct->firstItem) {
455 buildFlatList(co);
456 ct->firstItem = ct->currItem = ct->flatList;
457 }
458
459 item = ct->firstItem;
460
461 i = 0;
462
463 newtTrashScreen();
464
465 while (*item && i < co->height) {
466 newtGotorc(co->top + i, co->left);
467 SLsmg_set_color(NEWT_COLORSET_LISTBOX);
468 for (j = 0; j < (*item)->depth; j++)
469 SLsmg_write_string(" ");
470
471 if ((*item)->branch) {
472 if ((*item)->selected)
473 SLsmg_write_string("<-> ");
474 else
475 SLsmg_write_string("<+> ");
476 } else {
477 if (ct->flags & NEWT_CHECKBOXTREE_HIDE_BOX) {
478 if ((*item)->selected)
479 SLsmg_set_color(NEWT_COLORSET_SELLISTBOX);
480 SLsmg_write_string(" ");
481 } else {
482 char tmp[5];
483 snprintf(tmp,5,"[%c] ",ct->seq[(*item)->selected]);
484 SLsmg_write_string(tmp);
485 }
486 }
487 if (*item == *ct->currItem) {
488 SLsmg_set_color(ct->isActive ?
489 NEWT_COLORSET_ACTSELLISTBOX : NEWT_COLORSET_ACTLISTBOX);
490 currRow = co->top + i;
491 }
492
493 SLsmg_write_nstring((*item)->text, co->width - 4 -
494 (3 * (*item)->depth));
495
496 item++;
497 i++;
498 }
499
500 /* There could be empty lines left (i.e. if the user closes an expanded
501 list which is the last thing in the tree, and whose elements are
502 displayed at the bottom of the screen */
503 if (i < co->height) {
504 spaces = alloca(co->width);
505 memset(spaces, ' ', co->width);
506 SLsmg_set_color(NEWT_COLORSET_LISTBOX);
507 }
508 while (i < co->height) {
509 newtGotorc(co->top + i, co->left);
510 SLsmg_write_nstring(spaces, co->width);
511 i++;
512 }
513
514 if(ct->sb) {
515 newtScrollbarSet(ct->sb, ct->currItem - ct->flatList,
516 ct->flatCount - 1);
517 ct->sb->ops->draw(ct->sb);
518 }
519
520 newtGotorc(currRow, co->left + 1);
521 }
522
523 static void ctDestroy(newtComponent co) {
524 struct CheckboxTree * ct = co->data;
525 struct items * item, * nextitem;
526
527 nextitem = item = ct->itemlist;
528
529 while (item != NULL) {
530 nextitem = item->next;
531 free(item->text);
532 free(item);
533 item = nextitem;
534 }
535
536 free(ct->seq);
537 free(ct);
538 free(co);
539 }
540
541 static void ctEnsureLimits( struct CheckboxTree *ct ) {
542 struct items **listEnd = ct->flatList + ct->flatCount - 1;
543 if (ct->firstItem < ct->flatList)
544 ct->firstItem = ct->flatList;
545 if (ct->currItem < ct->flatList)
546 ct->currItem = ct->flatList;
547 if (ct->firstItem > listEnd) {
548 ct->firstItem = listEnd;
549 ct->currItem = listEnd;
550 }
551 }
552
553 struct eventResult ctEvent(newtComponent co, struct event ev) {
554 struct CheckboxTree * ct = co->data;
555 struct eventResult er;
556 struct items ** listEnd, ** lastItem;
557 int key, selnum = 1;
558
559 er.result = ER_IGNORED;
560
561 if(ev.when == EV_EARLY || ev.when == EV_LATE) {
562 return er;
563 }
564
565 switch(ev.event) {
566 case EV_KEYPRESS:
567 key = ev.u.key;
568 if (key == (char) key && key != ' ') {
569 for (selnum = 0; ct->seq[selnum]; selnum++)
570 if (key == ct->seq[selnum])
571 break;
572 if (!ct->seq[selnum])
573 switch (key) {
574 case '-': selnum = 0; break;
575 case '+':
576 case '*': selnum = 1; break;
577 }
578 if (ct->seq[selnum])
579 key = '*';
580 }
581 switch(key) {
582 case ' ':
583 case NEWT_KEY_ENTER:
584 ctSetItem(co, *ct->currItem, NEWT_FLAGS_TOGGLE);
585 er.result = ER_SWALLOWED;
586 if (!(*ct->currItem)->branch || (*ct->currItem)->selected)
587 key = NEWT_KEY_DOWN;
588 else
589 key = '*';
590 break;
591 case '*':
592 if ((*ct->currItem)->branch) {
593 ctSetItems((*ct->currItem)->branch, selnum);
594 if (!(*ct->currItem)->selected)
595 key = NEWT_KEY_DOWN;
596 } else {
597 (*ct->currItem)->selected = selnum;
598 key = NEWT_KEY_DOWN;
599 }
600 er.result = ER_SWALLOWED;
601 break;
602 }
603 switch (key) {
604 case '*':
605 ctDraw(co);
606 if(co->callback) co->callback(co, co->callbackData);
607 return er;
608 case NEWT_KEY_HOME:
609 ct->currItem = ct->flatList;
610 ct->firstItem = ct->flatList;
611 ctDraw(co);
612 if(co->callback) co->callback(co, co->callbackData);
613 er.result = ER_SWALLOWED;
614 return er;
615 case NEWT_KEY_END:
616 ct->currItem = ct->flatList + ct->flatCount - 1;
617 if (ct->flatCount <= co->height)
618 ct->firstItem = ct->flatList;
619 else
620 ct->firstItem = ct->flatList + ct->flatCount - co->height;
621 ctDraw(co);
622 if(co->callback) co->callback(co, co->callbackData);
623 er.result = ER_SWALLOWED;
624 return er;
625 case NEWT_KEY_DOWN:
626 if (ev.u.key != NEWT_KEY_DOWN) {
627 if(co->callback) co->callback(co, co->callbackData);
628 if (strlen(ct->seq) != 2) {
629 ctDraw(co);
630 return er;
631 }
632 }
633 if ((ct->currItem - ct->flatList + 1) < ct->flatCount) {
634 ct->currItem++;
635
636 if (ct->currItem - ct->firstItem >= co->height)
637 ct->firstItem++;
638
639 ctDraw(co);
640 } else if (ev.u.key != NEWT_KEY_DOWN)
641 ctDraw(co);
642 if(co->callback) co->callback(co, co->callbackData);
643 er.result = ER_SWALLOWED;
644 return er;
645 case NEWT_KEY_UP:
646 if (ct->currItem != ct->flatList) {
647 ct->currItem--;
648
649 if (ct->currItem < ct->firstItem)
650 ct->firstItem = ct->currItem;
651
652 ctDraw(co);
653 }
654 er.result = ER_SWALLOWED;
655 if(co->callback) co->callback(co, co->callbackData);
656 return er;
657 case NEWT_KEY_PGUP:
658 if (ct->firstItem - co->height < ct->flatList) {
659 ct->firstItem = ct->currItem = ct->flatList;
660 } else {
661 ct->currItem -= co->height;
662 ct->firstItem -= co->height;
663 }
664 ctEnsureLimits( ct );
665
666 ctDraw(co);
667 if(co->callback) co->callback(co, co->callbackData);
668 er.result = ER_SWALLOWED;
669 return er;
670 case NEWT_KEY_PGDN:
671 listEnd = ct->flatList + ct->flatCount - 1;
672 lastItem = ct->firstItem + co->height - 1;
673
674 if (lastItem + co->height > listEnd) {
675 ct->firstItem = listEnd - co->height + 1;
676 ct->currItem = listEnd;
677 } else {
678 ct->currItem += co->height;
679 ct->firstItem += co->height;
680 }
681 ctEnsureLimits( ct );
682
683 ctDraw(co);
684 if(co->callback) co->callback(co, co->callbackData);
685 er.result = ER_SWALLOWED;
686 return er;
687 }
688 break;
689
690 case EV_FOCUS:
691 ct->isActive = 1;
692 ctDraw(co);
693 er.result = ER_SWALLOWED;
694 break;
695
696 case EV_UNFOCUS:
697 ct->isActive = 0;
698 ctDraw(co);
699 er.result = ER_SWALLOWED;
700 break;
701 default:
702 break;
703 }
704
705 return er;
706 }
707
708 const void * newtCheckboxTreeGetCurrent(newtComponent co) {
709 struct CheckboxTree * ct = co->data;
710
711 if (!ct->currItem) return NULL;
712 return (*ct->currItem)->data;
713 }
714
715 void newtCheckboxTreeSetEntry(newtComponent co, const void * data, const char * text)
716 {
717 struct CheckboxTree * ct;
718 struct items * item;
719 int i, width;
720
721 if (!co) return;
722 ct = co->data;
723 item = findItem(ct->itemlist, data);
724 if (!item) return;
725
726 free(item->text);
727 item->text = strdup(text);
728
729 i = 4 + (3 * item->depth);
730
731 width = wstrlen(text, -1);
732 if ((ct->userHasSetWidth == 0) && ((width + i + ct->sbAdjust) > co->width)) {
733 updateWidth(co, ct, width + i);
734 }
735
736 ctDraw(co);
737 }
738
739 char newtCheckboxTreeGetEntryValue(newtComponent co, const void * data)
740 {
741 struct CheckboxTree * ct;
742 struct items * item;
743
744 if (!co) return -1;
745 ct = co->data;
746 item = findItem(ct->itemlist, data);
747 if (!item) return -1;
748 if (item->branch)
749 return item->selected ? NEWT_CHECKBOXTREE_EXPANDED : NEWT_CHECKBOXTREE_COLLAPSED;
750 else
751 return ct->seq[item->selected];
752 }
753
754 void newtCheckboxTreeSetEntryValue(newtComponent co, const void * data, char value)
755 {
756 struct CheckboxTree * ct;
757 struct items * item;
758 int i;
759
760 if (!co) return;
761 ct = co->data;
762 item = findItem(ct->itemlist, data);
763 if (!item || item->branch) return;
764
765 for(i = 0; ct->seq[i]; i++)
766 if (value == ct->seq[i])
767 break;
768
769 if (!ct->seq[i]) return;
770 item->selected = i;
771
772 ctDraw(co);
773 }
774
775
776 void newtCheckboxTreeSetCurrent(newtComponent co, void * data) {
777 struct CheckboxTree * ct = co->data;
778 int * path;
779 int i, j;
780 struct items * treeTop, * item;
781
782 path = newtCheckboxTreeFindItem(co, data);
783 if (!path) return;
784
785 /* traverse the path and turn on all of the branches to this point */
786 for (i = 0, treeTop = ct->itemlist; path[i + 1] != NEWT_ARG_LAST; i++) {
787 for (j = 0, item = treeTop; j < path[i]; j++)
788 item = item->next;
789
790 item->selected = 1;
791 treeTop = item->branch;
792 }
793
794 buildFlatList(co);
795
796 item = findItem(ct->itemlist, data);
797
798 i = 0;
799 while (ct->flatList[i] != item) i++;
800
801 /* choose the top item */
802 j = i - (co->height / 2);
803
804 if ((j + co->height) > ct->flatCount)
805 j = ct->flatCount - co->height;
806
807 if (j < 0)
808 j = 0;
809
810 ct->firstItem = ct->flatList + j;
811 ct->currItem = ct->flatList + i;
812
813 ctDraw(co);
814 }