OSDN Git Service

change version number
[bbk/bchanl.git] / src / extbbslist.c
1 /*
2  * extbbslist.c
3  *
4  * Copyright (c) 2012 project bchan
5  *
6  * This software is provided 'as-is', without any express or implied
7  * warranty. In no event will the authors be held liable for any damages
8  * arising from the use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not
15  *    claim that you wrote the original software. If you use this software
16  *    in a product, an acknowledgment in the product documentation would be
17  *    appreciated but is not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  *    misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source
23  *    distribution.
24  *
25  */
26
27 #include        <basic.h>
28 #include        <bstdlib.h>
29 #include        <bstdio.h>
30 #include        <bstring.h>
31 #include        <tstring.h>
32 #include        <tcode.h>
33 #include        <errcode.h>
34 #include        <btron/btron.h>
35 #include        <btron/dp.h>
36 #include        <bsys/queue.h>
37 #include        <tad.h>
38 #include        <mtstring.h>
39
40 #include    "extbbslist.h"
41
42 #include    <tad/taditerator.h>
43 #include    <tad/tadtsvparser.h>
44
45 #ifdef BCHANL_CONFIG_DEBUG
46 # define DP(arg) printf arg
47 # define DP_ER(msg, err) printf("%s (%d/%x)\n", msg, err>>16, err)
48 #else
49 # define DP(arg) /**/
50 # define DP_ER(msg, err) /**/
51 #endif
52
53 struct extbbslist_item_t_ {
54         QUEUE que;
55         TC *title;
56         W title_len;
57         struct {
58                 TC *tc;
59                 W tc_len;
60                 UB *asc;
61                 W asc_len;
62         } url;
63 };
64 typedef struct extbbslist_item_t_ extbbslist_item_t;
65
66 LOCAL extbbslist_item_t* extbbslist_item_nextnode(extbbslist_item_t *item)
67 {
68         return (extbbslist_item_t*)item->que.next;
69 }
70
71 LOCAL VOID extbbslist_item_QueInsert(extbbslist_item_t *entry, extbbslist_item_t *que)
72 {
73         QueInsert(&entry->que, &que->que);
74 }
75
76 LOCAL VOID extbbslist_item_QueRemove(extbbslist_item_t *entry)
77 {
78         QueRemove(&entry->que);
79         QueInit(&entry->que);
80 }
81
82 LOCAL Bool extbbslist_item_isQueEmpty(extbbslist_item_t *entry)
83 {
84         return isQueEmpty(&entry->que);
85 }
86
87 LOCAL Bool extbbslist_item_titlecheck(extbbslist_item_t *entry, TC *title, W title_len)
88 {
89         W cmp;
90
91         if (entry->title_len != title_len) {
92                 return False;
93         }
94         cmp = tc_strncmp(entry->title, title, title_len);
95         if (cmp != 0) {
96                 return False;
97         }
98         return True;
99 }
100
101 LOCAL VOID extbbslist_item_replacetitle(extbbslist_item_t *item, TC *title, W title_len)
102 {
103         if (item->title != NULL) {
104                 free(item->title);
105         }
106         item->title = title;
107         item->title_len = title_len;
108 }
109
110 LOCAL VOID extbbslist_item_replaceTCurl(extbbslist_item_t *item, TC *url, W url_len)
111 {
112         if (item->url.tc != NULL) {
113                 free(item->url.tc);
114         }
115         item->url.tc = url;
116         item->url.tc_len = url_len;
117 }
118
119 LOCAL VOID extbbslist_item_replaceascurl(extbbslist_item_t *item, UB *url, W url_len)
120 {
121         if (item->url.asc != NULL) {
122                 free(item->url.asc);
123         }
124         item->url.asc = url;
125         item->url.asc_len = url_len;
126 }
127
128 LOCAL W extbbslist_item_assigntitle(extbbslist_item_t *item, CONST TC *title, W title_len)
129 {
130         TC *str;
131         str = malloc(sizeof(TC)*(title_len+1));
132         if (str == NULL) {
133                 return -1; /* TODO */
134         }
135         memcpy(str, title, sizeof(TC)*title_len);
136         str[title_len] = TNULL;
137         extbbslist_item_replacetitle(item, str, title_len);
138         return 0;
139 }
140
141 LOCAL W extbbslist_item_assignurl(extbbslist_item_t *item, CONST TC *url, W url_len)
142 {
143         TC *str;
144         UB *str_ac;
145         W len;
146
147         str = malloc(sizeof(TC)*(url_len+1));
148         if (str == NULL) {
149                 return -1; /* TODO */
150         }
151         memcpy(str, url, sizeof(TC)*url_len);
152         str[url_len] = TNULL;
153
154         len = tcstosjs(NULL, str);
155         str_ac = malloc(sizeof(UB)*(len+1));
156         if (str_ac == NULL) {
157                 free(str);
158                 return -1; /* TODO */
159         }
160         tcstosjs(str_ac, str);
161         str_ac[len] = '\0';
162
163         extbbslist_item_replaceTCurl(item, str, url_len);
164         extbbslist_item_replaceascurl(item, str_ac, len);
165
166         return 0;
167 }
168
169 LOCAL W extbbslist_item_initialize(extbbslist_item_t *item)
170 {
171         QueInit(&item->que);
172         item->title = NULL;
173         item->title_len = 0;
174         item->url.tc = NULL;
175         item->url.tc_len = 0;
176         item->url.asc = NULL;
177         item->url.asc_len = 0;
178
179         return 0;
180 }
181
182 LOCAL VOID extbbslist_item_finalize(extbbslist_item_t *item)
183 {
184         if (item->url.asc != NULL) {
185                 free(item->url.asc);
186         }
187         if (item->url.tc != NULL) {
188                 free(item->url.tc);
189         }
190         if (item->title != NULL) {
191                 free(item->title);
192         }
193         QueRemove(&item->que);
194 }
195
196 LOCAL extbbslist_item_t* extbbslist_item_new()
197 {
198         extbbslist_item_t *item;
199         W err;
200
201         item = malloc(sizeof(extbbslist_item_t));
202         if (item == NULL) {
203                 return NULL;
204         }
205         err = extbbslist_item_initialize(item);
206         if (err < 0) {
207                 free(item);
208                 return NULL;
209         }
210         return item;
211 }
212
213 LOCAL VOID extbbslist_item_delete(extbbslist_item_t *item)
214 {
215         extbbslist_item_finalize(item);
216         free(item);
217 }
218
219 struct extbbslist_t_ {
220         QUEUE sentinel;
221         extbbslist_readcontext_t *ctx;
222         extbbslist_editcontext_t *edit;
223         W num;
224         LINK *lnk;
225         W rectype;
226         UH subtype;
227         Bool changed;
228 };
229
230 LOCAL extbbslist_item_t* extbbslist_sentinelnode(extbbslist_t *list)
231 {
232         return (extbbslist_item_t*)&list->sentinel;
233 }
234
235 LOCAL extbbslist_item_t* extbbslist_searchitem(extbbslist_t *list, TC *title, W title_len)
236 {
237         extbbslist_item_t *entry, *senti;
238         Bool ok;
239
240         senti = extbbslist_sentinelnode(list);
241         entry = extbbslist_item_nextnode(senti);
242         for (;;) {
243                 if (entry == senti) {
244                         break;
245                 }
246                 ok = extbbslist_item_titlecheck(entry, title, title_len);
247                 if (ok != False) {
248                         return entry;
249                 }
250                 entry = extbbslist_item_nextnode(entry);
251         }
252
253         return NULL;
254 }
255
256 EXPORT W extbbslist_appenditem(extbbslist_t *list, TC *title, W title_len, UB *url, W url_len)
257 {
258         extbbslist_item_t *item, *senti;
259         W len;
260         TC *str;
261         UB *str_ac;
262
263         if (list->ctx != NULL) {
264                 return -1; /* TODO */
265         }
266
267         item = extbbslist_searchitem(list, title, title_len);
268         if (item != NULL) {
269                 return -1; /* TODO */
270         }
271
272         item = extbbslist_item_new();
273         if (item == NULL) {
274                 return -1; /* TODO */
275         }
276
277         str = malloc(sizeof(TC)*(title_len+1));
278         if (str == NULL) {
279                 extbbslist_item_delete(item);
280                 return -1; /* TODO */
281         }
282         memcpy(str, title, sizeof(TC)*title_len);
283         str[title_len] = TNULL;
284         extbbslist_item_replacetitle(item, str, title_len);
285
286         str_ac = malloc(sizeof(UB)*(url_len+1));
287         if (str_ac == NULL) {
288                 extbbslist_item_delete(item);
289                 return -1; /* TODO */
290         }
291         memcpy(str_ac, url, sizeof(UB)*url_len);
292         str_ac[url_len] = '\0';
293         extbbslist_item_replaceascurl(item, str_ac, url_len);
294
295         len = sjstotcs(NULL, url);
296         str = malloc(sizeof(TC)*(len+1));
297         if (str == NULL) {
298                 extbbslist_item_delete(item);
299                 return -1;
300         }
301         sjstotcs(str, url);
302         str[len] = TNULL;
303         extbbslist_item_replaceTCurl(item, str, len);
304
305         senti = extbbslist_sentinelnode(list);
306         extbbslist_item_QueInsert(item, senti);
307         list->num++;
308         list->changed = True;
309
310         return 0;
311 }
312
313 EXPORT W extbbslist_deleteitem(extbbslist_t *list, TC *title, W title_len)
314 {
315         extbbslist_item_t *item;
316
317         if (list->ctx != NULL) {
318                 return -1; /* TODO */
319         }
320
321         item = extbbslist_searchitem(list, title, title_len);
322         if (item == NULL) {
323                 return -1; /* TODO */
324         }
325
326         extbbslist_item_delete(item);
327         list->num--;
328         list->changed = True;
329
330         return 0; /* TODO */
331 }
332
333 LOCAL VOID extbbslist_clear(extbbslist_t *list)
334 {
335         extbbslist_item_t *item;
336         Bool empty;
337
338         for (;;) {
339                 empty = isQueEmpty(&list->sentinel);
340                 if (empty == True) {
341                         break;
342                 }
343                 item = (extbbslist_item_t*)list->sentinel.prev;
344                 extbbslist_item_delete(item);
345         }
346         list->changed = True;
347 }
348
349 EXPORT W extbbslist_number(extbbslist_t *list)
350 {
351         return list->num;
352 }
353
354 EXPORT W extbbslist_writefile(extbbslist_t *list)
355 {
356         W fd, err;
357         UB bin[4+24];
358         TADSEG *base = (TADSEG*)bin;
359         INFOSEG *infoseg = (INFOSEG*)(bin + 4);
360         TEXTSEG *textseg = (TEXTSEG*)(bin + 4);
361         extbbslist_item_t *senti, *item;
362         TC tab[] = {0xFE21, TK_TAB};
363         TC nl[] = {0xFE21, TK_NL};
364
365         if (list->lnk == NULL) {
366                 return -1; /* TODO */
367         }
368
369         if (list->changed == False) {
370                 return 0;
371         }
372
373         fd = opn_fil(list->lnk, F_UPDATE, NULL);
374         if (fd < 0) {
375                 DP_ER("opn_fil", fd);
376                 return fd;
377         }
378
379         err = fnd_rec(fd, F_TOPEND, 1 << list->rectype, list->subtype, NULL);
380         if (err == ER_REC) {
381                 err = ins_rec(fd, NULL, 0, list->rectype, list->subtype, 0);
382                 if (err < 0) {
383                         DP_ER("ins_rec", err);
384                         cls_fil(fd);
385                         return err;
386                 }
387                 err = see_rec(fd, -1, 0, NULL);
388                 if (err < 0) {
389                         DP_ER("see_rec", err);
390                         cls_fil(fd);
391                         return err;
392                 }
393         } else if (err < 0) {
394                 DP_ER("fnd_rec", err);
395                 cls_fil(fd);
396                 return err;
397         }
398         err = trc_rec(fd, 0);
399         if (err < 0) {
400                 DP_ER("trc_rec", err);
401                 cls_fil(fd);
402                 return err;
403         }
404
405         base->id = 0xFFE0;
406         base->len = 6;
407         infoseg->subid = 0;
408         infoseg->sublen = 2;
409         infoseg->data[0] = 0x0122;
410         err = wri_rec(fd, -1, bin, 4+6, NULL, NULL, 0);
411         if (err < 0) {
412                 DP_ER("wri_rec:infoseg error", err);
413                 cls_fil(fd);
414                 return fd;
415         }
416         base->id = 0xFFE1;
417         base->len = 24;
418         textseg->view = (RECT){{0, 0, 0, 0}};
419         textseg->draw = (RECT){{0, 0, 0, 0}};
420         textseg->h_unit = -120;
421         textseg->v_unit = -120;
422         textseg->lang = 0x21;
423         textseg->bgpat = 0;
424         err = wri_rec(fd, -1, bin, 4+24, NULL, NULL, 0);
425         if (err < 0) {
426                 DP_ER("wri_rec:textseg error", err);
427                 cls_fil(fd);
428                 return fd;
429         }
430
431         senti = extbbslist_sentinelnode(list);
432         item = extbbslist_item_nextnode(senti);
433         for (;;) {
434                 if (item == senti) {
435                         break;
436                 }
437
438                 err = wri_rec(fd, -1, (UB*)item->title, item->title_len*sizeof(TC), NULL, NULL, 0);
439                 if (err < 0) {
440                         DP_ER("wri_rec:textseg error", err);
441                         cls_fil(fd);
442                         return fd;
443                 }
444                 err = wri_rec(fd, -1, (UB*)tab, 2*sizeof(TC), NULL, NULL, 0);
445                 if (err < 0) {
446                         DP_ER("wri_rec:textseg error", err);
447                         cls_fil(fd);
448                         return fd;
449                 }
450                 err = wri_rec(fd, -1, (UB*)item->url.tc, item->url.tc_len*sizeof(TC), NULL, NULL, 0);
451                 if (err < 0) {
452                         DP_ER("wri_rec:textseg error", err);
453                         cls_fil(fd);
454                         return fd;
455                 }
456                 err = wri_rec(fd, -1, (UB*)nl, 2*sizeof(TC), NULL, NULL, 0);
457                 if (err < 0) {
458                         DP_ER("wri_rec:textseg error", err);
459                         cls_fil(fd);
460                         return fd;
461                 }
462
463                 item = extbbslist_item_nextnode(item);
464         }
465
466         base->id = 0xFFE2;
467         base->len = 0;
468         err = wri_rec(fd, -1, bin, 4, NULL, NULL, 0);
469         if (err < 0) {
470                 DP_ER("wri_rec:textend error", err);
471                 cls_fil(fd);
472                 return fd;
473         }
474
475         cls_fil(fd);
476
477         return err;
478 }
479
480 struct extbbslist_parsetsv_ctx_t_ {
481         extbbslist_t *list;
482         W n_field;
483         struct {
484                 TC *str;
485                 W len;
486         } title;
487         struct {
488                 TC *str;
489                 W len;
490         } url;
491 };
492 typedef struct extbbslist_parsetsv_ctx_t_ extbbslist_parsetsv_ctx_t;
493
494 LOCAL W extbbslist_parsetsv_ctx_input(extbbslist_parsetsv_ctx_t *ctx, TADSTACK_RESULT psr_result, taditerator_result *seg_result)
495 {
496         TC *p;
497         extbbslist_item_t *item, *senti;
498         UB *str_ac;
499         W len;
500
501         if (psr_result == TADTSVPARSER_RESULT_FORMAT_ERROR) {
502                 return 0;
503         }
504         if (psr_result == TADTSVPARSER_RESULT_IGNORE_SEGMENT) {
505                 return 0;
506         }
507
508         if (psr_result == TADTSVPARSER_RESULT_FIELD) {
509                 if (seg_result == NULL) {
510                         DP(("seg_result == NULL\n"));
511                         return -1; /* TODO */
512                 }
513                 if (seg_result->type != TADITERATOR_RESULTTYPE_CHARCTOR) {
514                         return 0;
515                 }
516
517                 if (ctx->n_field == 0) {
518                         ctx->title.len++;
519                         p = (TC*)realloc(ctx->title.str, (ctx->title.len+1)*sizeof(TC));
520                         if (p == NULL) {
521                                 DP_ER("realloc:title", -1);
522                                 return -1; /* TODO */
523                         }
524                         ctx->title.str = p;
525                         ctx->title.str[ctx->title.len - 1] = seg_result->segment;
526                         ctx->title.str[ctx->title.len] = TNULL;
527                 } else if (ctx->n_field == 1) {
528                         ctx->url.len++;
529                         p = (TC*)realloc(ctx->url.str, (ctx->url.len+1)*sizeof(TC));
530                         if (p == NULL) {
531                                 DP_ER("realloc:url", -1);
532                                 return -1; /* TODO */
533                         }
534                         ctx->url.str = p;
535                         ctx->url.str[ctx->url.len - 1] = seg_result->segment;
536                         ctx->url.str[ctx->url.len] = TNULL;
537                 }
538         } else if (psr_result == TADTSVPARSER_RESULT_FIELD_END) {
539                 ctx->n_field++;
540         } else if (psr_result == TADTSVPARSER_RESULT_RECORD_END) {
541                 if (ctx->n_field < 1) {
542                         free(ctx->url.str);
543                         ctx->url.str = NULL;
544                         ctx->url.len = 0;
545                         free(ctx->title.str);
546                         ctx->title.str = NULL;
547                         ctx->title.len = 0;
548                         ctx->n_field = 0;
549                         return 0;
550                 }
551                 ctx->n_field = 0;
552
553                 ctx->title.len = mtc_unique(ctx->title.str, ctx->title.str, ctx->title.len);
554                 ctx->url.len = mtc_unique(ctx->url.str, ctx->url.str, ctx->url.len);
555
556                 item = extbbslist_item_new();
557                 if (item == NULL) {
558                         DP_ER("extbbslist_item_new", -1);
559                         return -1; /* TODO */
560                 }
561
562                 len = tcstosjs(NULL, ctx->url.str);
563                 str_ac = malloc(sizeof(UB)*(len+1));
564                 if (str_ac == NULL) {
565                         DP_ER("malloc", -1);
566                         return -1; /* TODO */
567                 }
568                 tcstosjs(str_ac, ctx->url.str);
569                 str_ac[len] = '\0';
570
571                 extbbslist_item_replacetitle(item, ctx->title.str, ctx->title.len);
572                 ctx->title.str = NULL;
573                 ctx->title.len = 0;
574
575                 extbbslist_item_replaceTCurl(item, ctx->url.str, ctx->url.len);
576                 ctx->url.str = NULL;
577                 ctx->url.len = 0;
578
579                 extbbslist_item_replaceascurl(item, str_ac, len);
580
581                 senti = extbbslist_sentinelnode(ctx->list);
582                 extbbslist_item_QueInsert(item, senti);
583                 ctx->list->num++;
584         }
585
586         return 0;
587 }
588
589 LOCAL VOID extbbslist_parsetsv_ctx_initialize(extbbslist_parsetsv_ctx_t *ctx, extbbslist_t *list)
590 {
591         ctx->list = list;
592         ctx->n_field = 0;
593         ctx->title.str = NULL;
594         ctx->title.len = 0;
595         ctx->url.str = NULL;
596         ctx->url.len = 0;
597 }
598
599 LOCAL VOID extbbslist_parsetsv_ctx_finalize(extbbslist_parsetsv_ctx_t *ctx)
600 {
601         if (ctx->title.str != NULL) {
602                 free(ctx->title.str);
603         }
604         if (ctx->url.str != NULL) {
605                 free(ctx->url.str);
606         }
607 }
608
609 LOCAL W extbbslist_parserecord(extbbslist_t *list, UB *rec, W len)
610 {
611         tadtsvparser_t parser;
612         taditerator_t iter;
613         taditerator_result result;
614         TADSTACK_RESULT psr_result;
615         W err = 0;
616         extbbslist_parsetsv_ctx_t ctx;
617         
618         tadtsvparser_initialize(&parser);
619         taditerator_initialize(&iter, (TC*)rec, len/sizeof(TC));
620         extbbslist_parsetsv_ctx_initialize(&ctx, list);
621
622         for (;;) {
623                 taditerator_next2(&iter, &result);
624                 if (result.type == TADITERATOR_RESULTTYPE_END) {
625                         break;
626                 }
627
628                 if (result.type == TADITERATOR_RESULTTYPE_CHARCTOR) {
629                         psr_result = tadtsvparser_inputcharactor(&parser, result.segment);
630                 } else if (result.type == TADITERATOR_RESULTTYPE_SEGMENT) {
631                         psr_result = tadtsvparser_inputvsegment(&parser, result.segment, result.data, result.segsize);
632                 } else {
633                         psr_result = TADTSVPARSER_RESULT_IGNORE_SEGMENT;
634                 }
635
636                 err = extbbslist_parsetsv_ctx_input(&ctx, psr_result, &result);
637                 if (err < 0) {
638                         DP_ER("extbbslist_parsetsv_ctx_input", err);
639                         break;
640                 }
641         }
642         psr_result = tadtsvparser_inputendofdata(&parser);
643         err = extbbslist_parsetsv_ctx_input(&ctx, psr_result, NULL);
644
645         extbbslist_parsetsv_ctx_finalize(&ctx);
646         taditerator_finalize(&iter);
647         tadtsvparser_finalize(&parser);
648
649         return err;
650 }
651
652 EXPORT W extbbslist_readfile(extbbslist_t *list)
653 {
654         UB *rec;
655         W fd, err, rec_len;
656
657         fd = opn_fil(list->lnk, F_UPDATE, NULL);
658         if (fd < 0) {
659                 DP_ER("opn_fil", fd);
660                 return fd;
661         }
662
663         err = fnd_rec(fd, F_TOPEND, 1 << list->rectype, list->subtype, NULL);
664         if (err < 0) {
665                 cls_fil(fd);
666                 if (err == ER_REC) {
667                         return 0;
668                 }
669                 return err;
670         }
671
672         err = rea_rec(fd, 0, NULL, 0, &rec_len, NULL);
673         if (err < 0) {
674                 cls_fil(fd);
675                 return err;
676         }
677         rec = (UB*)malloc(rec_len);
678         if (rec == NULL) {
679                 cls_fil(fd);
680                 return err;
681         }
682         err = rea_rec(fd, 0, rec, rec_len, NULL, NULL);
683         if (err < 0) {
684                 free(rec);
685                 cls_fil(fd);
686                 return err;
687         }
688
689         err = extbbslist_parserecord(list, rec, rec_len);
690
691         free(rec);
692         cls_fil(fd);
693
694         return err;
695 }
696
697 struct extbbslist_editcontext_t_ {
698         QUEUE sentinel;
699         W view_l, view_t, view_r, view_b;
700         W num;
701         struct {
702                 extbbslist_item_t *item;
703                 W index;
704         } selected;
705         Bool changed;
706 };
707
708 #define EXTBBSLIST_ENTRY_HEIGHT 20
709 #define EXTBBSLIST_ENTRY_PADDING_TOP 1
710
711 #define EXTBBSLIST_TITLE_WIDTH 128
712
713 LOCAL extbbslist_item_t* extbbslist_editcontext_sentinelnode(extbbslist_editcontext_t *ctx)
714 {
715         return (extbbslist_item_t*)&ctx->sentinel;
716 }
717
718 LOCAL extbbslist_item_t *extbbslist_editcontext_searchitembyindex(extbbslist_editcontext_t *ctx, W index)
719 {
720         W i;
721         extbbslist_item_t *senti, *item;
722
723         senti = extbbslist_editcontext_sentinelnode(ctx);
724         item = extbbslist_item_nextnode(senti);
725         for (i = 0; item != senti; i++) {
726                 if (i == index) {
727                         return item;
728                 }
729                 item = extbbslist_item_nextnode(item);
730         }
731         return NULL;
732 }
733
734 LOCAL W extbbslist_editcontext_append_common(extbbslist_editcontext_t *ctx, CONST TC *title, W title_len, CONST TC *url, W url_len)
735 {
736         extbbslist_item_t *item, *senti;
737         W err;
738
739         item = extbbslist_item_new();
740         if (item == NULL) {
741                 return -1; /* TODO */
742         }
743
744         err = extbbslist_item_assigntitle(item, title, title_len);
745         if (err < 0) {
746                 extbbslist_item_delete(item);
747                 return err;
748         }
749
750         err = extbbslist_item_assignurl(item, url, url_len);
751         if (err < 0) {
752                 extbbslist_item_delete(item);
753                 return err;
754         }
755
756         senti = extbbslist_editcontext_sentinelnode(ctx);
757         extbbslist_item_QueInsert(item, senti);
758         ctx->num++;
759
760         return 0;
761 }
762
763 EXPORT W extbbslist_editcontext_append(extbbslist_editcontext_t *ctx, CONST TC *title, W title_len, CONST TC *url, W url_len)
764 {
765         W err;
766
767         err = extbbslist_editcontext_append_common(ctx, title, title_len, url, url_len);
768         if (err < 0) {
769                 return err;
770         }
771         ctx->changed = True;
772         return 0;
773 }
774
775 EXPORT W extbbslist_editcontext_update(extbbslist_editcontext_t *ctx, W i, CONST TC *title, W title_len, CONST TC *url, W url_len)
776 {
777         extbbslist_item_t *item;
778         W err;
779
780         item = extbbslist_editcontext_searchitembyindex(ctx, i);
781         if (item == NULL) {
782                 return -1; /* TODO */
783         }
784
785         ctx->changed = True;
786
787         err = extbbslist_item_assigntitle(item, title, title_len);
788         if (err < 0) {
789                 return err;
790         }
791
792         err = extbbslist_item_assignurl(item, url, url_len);
793         if (err < 0) {
794                 return err;
795         }
796
797         return 0;
798 }
799
800 LOCAL W extbbslist_editcontext_drawcolseparater(extbbslist_editcontext_t *ctx, GID target, W x_start, W y, W height, PAT *lnpat)
801 {
802         PNT p1, p2;
803
804         p1.y = y;
805         p2.y = p1.y + height - 1;
806
807         p1.x = x_start + EXTBBSLIST_TITLE_WIDTH + 4;
808         p2.x = p1.x;
809         gdra_lin(target, p1, p2, 1, lnpat, G_STORE);
810
811         p1.y = p2.y;
812         p1.x = 0;
813         p2.x = ctx->view_r - ctx->view_l;
814         gdra_lin(target, p1, p2, 1, lnpat, G_STORE);
815
816         return 0;
817 }
818
819 LOCAL W extbbslist_editcontext_drawitemtext(extbbslist_editcontext_t *ctx, GID target, W x_start, W y, PAT *lnpat, extbbslist_item_t *item)
820 {
821         SIZE sz;
822         FSSPEC fspec;
823         W y2;
824
825         gset_scr(target, 0x21);
826
827         gget_fon(target, &fspec, NULL);
828         sz = fspec.size;
829         fspec.size.h = fspec.size.v;
830         gset_fon(target, &fspec);
831
832         y2 = y + sz.v + EXTBBSLIST_ENTRY_PADDING_TOP;
833         gdra_stp(target, x_start, y2, item->title, item->title_len, G_STORE);
834
835         gset_scr(target, 0x21);
836
837         fspec.size.h = fspec.size.v / 2;
838         gset_fon(target, &fspec);
839
840         gdra_stp(target, x_start + EXTBBSLIST_TITLE_WIDTH + 8 , y2, item->url.tc, item->url.tc_len, G_STORE);
841
842         extbbslist_editcontext_drawcolseparater(ctx, target, x_start, y, EXTBBSLIST_ENTRY_HEIGHT, lnpat);
843
844         return 0;
845 }
846
847 EXPORT W extbbslist_editcontext_draw(extbbslist_editcontext_t *ctx, GID target, RECT *r)
848 {
849         W i, x, y;
850         extbbslist_item_t *senti, *item;
851         PAT bgpat = {{
852                 0,
853                 16, 16,
854                 0x10efefef,
855                 0x10efefef,
856                 FILL100
857         }};
858         PAT lnpat = {{
859                 0,
860                 16, 16,
861                 0x10000000,
862                 0x10efefef,
863                 FILL100
864         }};
865         RECT bgr;
866
867         senti = extbbslist_editcontext_sentinelnode(ctx);
868         item = extbbslist_item_nextnode(senti);
869         for (i = 0; ; i++) {
870                 if (item == senti) {
871                         break;
872                 }
873                 if ((ctx->view_t <= (i + 1) * EXTBBSLIST_ENTRY_HEIGHT)&&(i * EXTBBSLIST_ENTRY_HEIGHT <= ctx->view_b)) {
874                         if (ctx->selected.item == item) {
875                                 bgpat.spat.fgcol = 0x10000000;
876                                 lnpat.spat.fgcol = 0x10FFFFFF;
877                                 gset_chc(target, 0x10FFFFFF, 0x10000000);
878                         } else {
879                                 bgpat.spat.fgcol = 0x10FFFFFF;
880                                 lnpat.spat.fgcol = 0x10000000;
881                                 gset_chc(target, 0x10000000, 0x10FFFFFF);
882                         }
883                         bgr.c.left = ctx->view_l;
884                         bgr.c.top = i * EXTBBSLIST_ENTRY_HEIGHT - ctx->view_t;
885                         bgr.c.right = ctx->view_r;
886                         bgr.c.bottom = (i+1) * EXTBBSLIST_ENTRY_HEIGHT - ctx->view_t;
887                         gfil_rec(target, bgr, &bgpat, 0, G_STORE);
888
889                         x = 0 - ctx->view_l;
890                         y = i * EXTBBSLIST_ENTRY_HEIGHT - ctx->view_t;
891                         extbbslist_editcontext_drawitemtext(ctx, target, x, y, &lnpat, item);
892                 }
893
894                 item = extbbslist_item_nextnode(item);
895         }
896
897         return 0;
898 }
899
900 EXPORT Bool extbbslist_editcontext_finditem(extbbslist_editcontext_t *ctx, PNT rel_pos, W *index)
901 {
902         W n;
903         n = (rel_pos.y + ctx->view_t) / EXTBBSLIST_ENTRY_HEIGHT;
904         if (n < ctx->num) {
905                 *index = n;
906                 return True;
907         }
908         return False;
909 }
910
911 IMPORT W extbbslist_editcontext_swapitem(extbbslist_editcontext_t *ctx, W i0, W i1)
912 {
913         extbbslist_item_t *item0, *item1, *item0_next, *item1_next;
914         W buf;
915
916         if (i0 == i1) {
917                 return 0;
918         }
919         if (i0 > i1) {
920                 buf = i1;
921                 i1 = i0;
922                 i0 = buf;
923         }
924
925         item0 = extbbslist_editcontext_searchitembyindex(ctx, i0);
926         if (item0 == NULL) {
927                 return -1;
928         }
929         item0_next = extbbslist_item_nextnode(item0);
930         item1 = extbbslist_editcontext_searchitembyindex(ctx, i1);
931         if (item1 == NULL) {
932                 return -1;
933         }
934         item1_next = extbbslist_item_nextnode(item1);
935
936         if (ctx->selected.index == i0) {
937                 ctx->selected.index = i1;
938         } else if (ctx->selected.index == i1) {
939                 ctx->selected.index = i0;
940         }
941         ctx->changed = True;
942
943         if (i0 + 1 == i1) {
944                 extbbslist_item_QueRemove(item1);
945                 extbbslist_item_QueInsert(item1, item0);
946                 return 0;
947         }
948
949         extbbslist_item_QueRemove(item1);
950         extbbslist_item_QueInsert(item1, item0_next);
951         extbbslist_item_QueRemove(item0);
952         extbbslist_item_QueInsert(item0, item1_next);
953
954         return 0;
955 }
956
957 IMPORT W extbbslist_editcontext_deleteitem(extbbslist_editcontext_t *ctx, W i)
958 {
959         extbbslist_item_t *item;
960
961         item = extbbslist_editcontext_searchitembyindex(ctx, i);
962         if (item == NULL) {
963                 return -1; /* TODO */
964         }
965         extbbslist_item_delete(item);
966         ctx->num--;
967         ctx->changed = True;
968         if (ctx->selected.index == i) {
969                 ctx->selected.item = NULL;
970                 ctx->selected.index = -1;
971         }
972         if (ctx->selected.index > i) {
973                 ctx->selected.index--;
974         }
975         return 0;
976 }
977
978 IMPORT VOID extbbslist_editcontext_setselect(extbbslist_editcontext_t *ctx, W i)
979 {
980         extbbslist_item_t *item;
981
982         item = extbbslist_editcontext_searchitembyindex(ctx, i);
983         if (item == NULL) {
984                 ctx->selected.item = NULL;
985                 ctx->selected.index = -1;
986                 return;
987         }
988         ctx->selected.item = item;
989         ctx->selected.index = i;
990 }
991
992 EXPORT W extbbslist_editcontext_getselect(extbbslist_editcontext_t *ctx)
993 {
994         return ctx->selected.index;
995 }
996
997 EXPORT Bool extbbslist_editcontext_ischanged(extbbslist_editcontext_t *ctx)
998 {
999         return ctx->changed;
1000 }
1001
1002 EXPORT VOID extbbslist_editcontext_setviewrect(extbbslist_editcontext_t *ctx, W l, W t, W r, W b)
1003 {
1004         ctx->view_l = l;
1005         ctx->view_t = t;
1006         ctx->view_r = r;
1007         ctx->view_b = b;
1008 }
1009
1010 EXPORT VOID extbbslist_editcontext_getviewrect(extbbslist_editcontext_t *ctx, W *l, W *t, W *r, W *b)
1011 {
1012         *l = ctx->view_l;
1013         *t = ctx->view_t;
1014         *r = ctx->view_r;
1015         *b = ctx->view_b;
1016 }
1017
1018 EXPORT VOID extbbslist_editcontext_scrollviewrect(extbbslist_editcontext_t *ctx, W dh, W dv)
1019 {
1020         ctx->view_l += dh;
1021         ctx->view_t += dv;
1022         ctx->view_r += dh;
1023         ctx->view_b += dv;
1024 }
1025
1026 EXPORT VOID extbbslist_editcontext_getdrawrect(extbbslist_editcontext_t *ctx, GID gid, W *l, W *t, W *r, W *b)
1027 {
1028         extbbslist_item_t *senti, *item;
1029         W max = 0, width;
1030         FSSPEC fspec;
1031
1032         gget_fon(gid, &fspec, NULL);
1033         fspec.size.h = fspec.size.v / 2;
1034         gset_fon(gid, &fspec);
1035
1036         senti = extbbslist_editcontext_sentinelnode(ctx);
1037         item = extbbslist_item_nextnode(senti);
1038         for (;;) {
1039                 if (item == senti) {
1040                         break;
1041                 }
1042
1043                 width = gget_stw(gid, item->url.tc, item->url.tc_len, NULL, NULL);
1044                 if (width < 0) {
1045                         continue;
1046                 }
1047                 if (width > max) {
1048                         max = width;
1049                 }
1050
1051                 item = extbbslist_item_nextnode(item);
1052         }
1053
1054         *l = 0;
1055         *t = 0;
1056         *r = EXTBBSLIST_TITLE_WIDTH + 8 + max + 4;
1057         *b = EXTBBSLIST_ENTRY_HEIGHT * ctx->num;
1058 }
1059
1060 LOCAL extbbslist_editcontext_t* extbbslist_editcontext_new()
1061 {
1062         extbbslist_editcontext_t *ctx;
1063
1064         ctx = (extbbslist_editcontext_t*)malloc(sizeof(extbbslist_editcontext_t));
1065         if (ctx == NULL) {
1066                 return NULL;
1067         }
1068         QueInit(&ctx->sentinel);
1069         ctx->view_l = 0;
1070         ctx->view_t = 0;
1071         ctx->view_r = 0;
1072         ctx->view_b = 0;
1073         ctx->num = 0;
1074         ctx->selected.item = NULL;
1075         ctx->selected.index = -1;
1076         ctx->changed = False;
1077
1078         return ctx;
1079 }
1080
1081 LOCAL VOID extbbslist_editcontext_delete(extbbslist_editcontext_t *ctx)
1082 {
1083         extbbslist_item_t *item;
1084         Bool empty;
1085
1086         for (;;) {
1087                 empty = isQueEmpty(&ctx->sentinel);
1088                 if (empty == True) {
1089                         break;
1090                 }
1091                 item = (extbbslist_item_t*)ctx->sentinel.prev;
1092                 extbbslist_item_delete(item);
1093         }
1094         free(ctx);
1095 }
1096
1097 EXPORT extbbslist_editcontext_t* extbbslist_startedit(extbbslist_t *list)
1098 {
1099         extbbslist_editcontext_t *ctx;
1100         extbbslist_item_t *senti, *item;
1101         W err;
1102
1103         if (list->ctx != NULL) {
1104                 return NULL;
1105         }
1106         if (list->edit != NULL) {
1107                 return NULL;
1108         }
1109
1110         ctx = extbbslist_editcontext_new();
1111         if (ctx == NULL) {
1112                 return NULL;
1113         }
1114
1115         senti = extbbslist_sentinelnode(list);
1116         item = extbbslist_item_nextnode(senti);
1117         for (; item != senti;) {
1118                 err = extbbslist_editcontext_append_common(ctx, item->title, item->title_len, item->url.tc, item->url.tc_len);
1119                 if (err < 0) {
1120                         extbbslist_editcontext_delete(ctx);
1121                         return NULL;
1122                 }
1123                 item = extbbslist_item_nextnode(item);
1124         }
1125
1126         list->edit = ctx;
1127
1128         return ctx;
1129 }
1130
1131 EXPORT VOID extbbslist_endedit(extbbslist_t *list, extbbslist_editcontext_t *ctx, Bool update)
1132 {
1133         extbbslist_item_t *sentinel, *next;
1134         Bool empty;
1135
1136         if ((update != False)&&(ctx->changed != False)) {
1137                 extbbslist_clear(list);
1138
1139                 sentinel = extbbslist_editcontext_sentinelnode(ctx);
1140
1141                 empty = extbbslist_item_isQueEmpty(sentinel);
1142                 if (empty == False) {
1143                         next = extbbslist_item_nextnode(sentinel);
1144                         extbbslist_item_QueRemove(sentinel);
1145
1146                         sentinel = extbbslist_sentinelnode(list);
1147                         extbbslist_item_QueInsert(sentinel, next);
1148
1149                         list->num = ctx->num;
1150                 } else {
1151                         list->num = 0;
1152                 }
1153                 list->changed = True;
1154         }
1155
1156         list->edit = NULL;
1157         extbbslist_editcontext_delete(ctx);
1158 }
1159
1160 struct extbbslist_readcontext_t_ {
1161         extbbslist_item_t *sentinel;
1162         extbbslist_item_t *curr;
1163 };
1164
1165 EXPORT Bool extbbslist_readcontext_getnext(extbbslist_readcontext_t *ctx, TC **title, W *title_len, UB **url, W *url_len)
1166 {
1167         if (ctx->curr == ctx->sentinel) {
1168                 return False;
1169         }
1170         *title = ctx->curr->title;
1171         *title_len = ctx->curr->title_len;
1172         *url = ctx->curr->url.asc;
1173         *url_len = ctx->curr->url.asc_len;
1174         ctx->curr = extbbslist_item_nextnode(ctx->curr);
1175         return True;
1176 }
1177
1178 LOCAL extbbslist_readcontext_t* extbbslist_readcontext_new(extbbslist_item_t *senti, extbbslist_item_t *curr)
1179 {
1180         extbbslist_readcontext_t *ctx;
1181
1182         ctx = (extbbslist_readcontext_t*)malloc(sizeof(extbbslist_readcontext_t));
1183         if (ctx == NULL) {
1184                 return NULL;
1185         }
1186         ctx->sentinel = senti;
1187         ctx->curr = curr;
1188
1189         return ctx;
1190 }
1191
1192 LOCAL VOID extbbslist_readcontext_delete(extbbslist_readcontext_t *ctx)
1193 {
1194         free(ctx);
1195 }
1196
1197 EXPORT extbbslist_readcontext_t* extbbslist_startread(extbbslist_t *list)
1198 {
1199         extbbslist_readcontext_t *ctx;
1200         extbbslist_item_t *senti, *curr;
1201
1202         senti = extbbslist_sentinelnode(list);
1203         curr = extbbslist_item_nextnode(senti);
1204         ctx = extbbslist_readcontext_new(senti, curr);
1205         if (ctx == NULL) {
1206                 return NULL;
1207         }
1208         list->ctx = ctx;
1209         return ctx;
1210 }
1211
1212 EXPORT VOID extbbslist_endread(extbbslist_t *list, extbbslist_readcontext_t *ctx)
1213 {
1214         list->ctx = NULL;
1215         extbbslist_readcontext_delete(ctx);
1216 }
1217
1218 LOCAL VOID extbbslist_initialize(extbbslist_t *list, LINK *db_link, W rectype, UH subtype)
1219 {
1220         QueInit(&list->sentinel);
1221         list->ctx = NULL;
1222         list->edit = NULL;
1223         list->num = 0;
1224         list->lnk = db_link;
1225         list->rectype = rectype;
1226         list->subtype = subtype;
1227         list->changed = False;
1228 }
1229
1230 LOCAL VOID extbbslist_finalize(extbbslist_t *list)
1231 {
1232         extbbslist_clear(list);
1233 }
1234
1235 EXPORT extbbslist_t* extbbslist_new(LINK *db_link, W rectype, UH subtype)
1236 {
1237         extbbslist_t *list;
1238
1239         list = malloc(sizeof(extbbslist_t));
1240         if (list == NULL) {
1241                 return NULL;
1242         }
1243         extbbslist_initialize(list, db_link, rectype, subtype);
1244         return list;
1245 }
1246
1247 EXPORT VOID extbbslist_delete(extbbslist_t *list)
1248 {
1249         extbbslist_finalize(list);
1250         free(list);
1251 }