1 /*
2 * rolo - contact management software
3 * Copyright (C) 2003 Andrew Hsu
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (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., 59 Temple Place, Suite 330, Boston, MA
18 * 02111-1307 USA
19 *
20 * $Id: index.c,v 1.20 2003/05/22 09:10:26 ahsu Rel $
21 */
22
23 #include "entry.h"
24 #include "index.h"
25 #include "search.h"
26
27 #include <assert.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <vc.h>
31
32 #define MENU_BAR_STRING "q:Quit v:View e:Edit a:Add d:Delete h:Help"
33 #ifndef CTRL
34 #define CTRL(x) ((x) & 0x1f)
35 #endif
36
37 /*** STATIC PROTOTYPES ***/
38
39 static void print_footer (const char *filename, int entries);
40 static void print_header ();
41 static void index_search ();
42 static void index_search_next ();
43 static void index_search_prev ();
44
45 static ITEM **get_items (entry_node ** entries, int count);
46 static void filter_menu ();
47 static void unfilter_menu ();
48 static void sort_ascending (int sort_by);
49 static void sort_descending (int sort_by);
50 static int scroll_to_result (ITEM * found_item);
51 static void clear_message_bar (MENU * m);
52
53 /*** STATIC VARIABLES ***/
54
55 static void (*display_help) (void);
56 static MENU *menu = NULL;
57 static WINDOW *win = NULL;
58 static WINDOW *sub = NULL;
59 static const char *datafile = NULL;
60 static char *filter_string = NULL;
61 static ITEM **items = NULL;
62 static g_sort_by = -1;
63
64 /***************************************************************************
65 */
66
67 void
finish_index()68 finish_index ()
69 {
70 unpost_menu (menu);
71 free_menu (menu);
72 menu = NULL;
73 free_items (items);
74 items = NULL;
75 }
76
77 /***************************************************************************
78 Initializes the index window by openning the data file,
79 populating the menu items, and setting up the header and footer.
80 */
81
82 void
init_index(const char * filename)83 init_index (const char *filename)
84 {
85 int count = 0;
86 int deleted = 0;
87 int rows = 0;
88 int cols = 0;
89 FILE *fp = NULL;
90 entry_node *entries_ll = NULL;
91 entry_node **entries_array = NULL;
92
93 /* have to get the win and sub pointers setup early */
94 win = stdscr;
95 scale_menu (menu, &rows, &cols); /* get the size required for menu */
96 sub = derwin (win, rows, cols, 1, 0);
97
98 /* remember: first node is a dummy node */
99 entries_ll = new_entry_node ();
100
101 datafile = filename;
102 fp = fopen (datafile, "r");
103 if (NULL == fp)
104 {
105 endwin (); /* exit gracefully */
106 fprintf (stderr, "unable to open file: %s\n", filename);
107 exit (1);
108 }
109
110 count = count_vcards (fp);
111
112 rewind (fp);
113 /* this should get a linked list */
114 count = get_entries (fp, entries_ll);
115 fclose (fp);
116
117 /* this should reduce the linked list if need be */
118 deleted = filter_entries (entries_ll, filter_string);
119
120 count -= deleted;
121
122 /* index the linked list in an array */
123 entries_array = entries_lltoa (entries_ll, count);
124 free_entry_node (entries_ll);
125 entries_ll = NULL;
126
127 /* use the array to sort */
128 if (-1 == g_sort_by)
129 {
130 g_sort_by = SORT_ENTRIES_BY_FAMILY_N;
131 }
132 sort_entries (entries_array, count, g_sort_by);
133
134 /* construct the items array and cleanup */
135 items = get_items (entries_array, count);
136 free_entries_array_but_leave_data (entries_array, count);
137 entries_array = NULL;
138
139 menu = get_menu (items);
140
141 /* set the menu hook to clear the message bar */
142 set_item_init (menu, clear_message_bar);
143
144 /* initialize the menu_userptr to NULL (to be used as search string) */
145 set_menu_userptr (menu, NULL);
146
147 print_header ();
148 print_footer (filename, count);
149
150 set_menu_win (menu, win);
151 set_menu_sub (menu, sub);
152 post_menu (menu);
153 }
154
155 /***************************************************************************
156 */
157
158 static ITEM **
get_items(entry_node ** entries,int count)159 get_items (entry_node ** entries, int count)
160 {
161 int i = 0;
162 ITEM **items;
163 char *line_number = NULL;
164
165 items = (ITEM **) malloc (sizeof (ITEM *) * (count + 1));
166
167 for (i = 0; i < count; i++)
168 {
169 line_number = (char *) malloc (sizeof (char) * 4);
170 sprintf (line_number, "%3i", (i + 1) % 1000);
171 items[i] = new_item (line_number, (entries[i])->description);
172 set_item_userptr (items[i], (entries[i])->fpos);
173 }
174
175 items[i] = (ITEM *) NULL;
176
177 return items;
178 }
179
180 /***************************************************************************
181 Cleans up the two-dimensional array of items.
182 */
183
184 void
free_items(ITEM ** items)185 free_items (ITEM ** items)
186 {
187 char *name = NULL;
188 char *description = NULL;
189 fpos_t *fpos = NULL;
190 int i = 0;
191
192 while ((ITEM *) NULL != items[i])
193 {
194 name = (char *) item_name (items[i]);
195 free (name);
196
197 description = (char *) item_description (items[i]);
198 free (description);
199
200 fpos = (fpos_t *) item_userptr (items[i]);
201 free (fpos);
202
203 free_item (items[i]);
204 i++;
205 }
206
207 free (items);
208 }
209
210 /***************************************************************************
211 Sets up a new menu.
212 */
213
214 MENU *
get_menu(ITEM ** items)215 get_menu (ITEM ** items)
216 {
217 MENU *menu = NULL;
218 int x = 0;
219 int y = 0;
220
221 getmaxyx (win, y, x);
222 menu = new_menu (items);
223 set_menu_format (menu, y - 3, 1); /* LINE rows, 1 column */
224
225 return menu;
226 }
227
228 /***************************************************************************
229 Displays the header for the index window.
230 */
231
232 static void
print_header()233 print_header ()
234 {
235 char *header_str = NULL;
236 int i = 0;
237 int x = 0;
238 int y = 0;
239
240 getmaxyx (win, y, x);
241 header_str = (char *) malloc (sizeof (char) * (x + 1));
242
243 strncpy (header_str, MENU_BAR_STRING, x);
244
245 for (i = strlen (MENU_BAR_STRING); i < x; i++)
246 {
247 header_str[i] = ' ';
248 }
249
250 header_str[x] = '\0';
251
252 wattron (win, A_REVERSE);
253 mvwprintw (win, 0, 0, header_str);
254 wstandend (win);
255 free (header_str);
256 }
257
258 /***************************************************************************
259 Displays a footer that will adjust its contents based on the
260 width of the screen.
261 */
262
263 static void
print_footer(const char * filename,int entries)264 print_footer (const char *filename, int entries)
265 {
266 char *footer_str = NULL;
267 char *rolo_block = NULL;
268 int rolo_block_len = 0;
269 char *entries_block = NULL;
270 int entries_block_len = 0;
271 int i = 0;
272 int j = 0;
273 int x = 0;
274 int y = 0;
275
276 getmaxyx (win, y, x);
277 footer_str = (char *) malloc (sizeof (char) * (x + 1));
278
279 /*
280 * set a default footer of all dashes
281 */
282
283 for (i = 0; i < x; i++)
284 {
285 footer_str[i] = '-';
286 }
287
288 rolo_block_len = strlen (filename) + 16;
289 entries_block_len = 22;
290
291 /*
292 * add the `rolo' block only if there is enough room
293 */
294
295 if (rolo_block_len + entries_block_len <= x)
296 {
297 rolo_block = (char *) malloc (sizeof (char) * (rolo_block_len + 1));
298 sprintf (rolo_block, "---[ rolo: %s ]---", filename);
299
300 for (i = 0; i < rolo_block_len; i++)
301 {
302 footer_str[i] = rolo_block[i];
303 }
304
305 free (rolo_block);
306 }
307
308 /*
309 * add the `entries' block only if there is enough room
310 */
311
312 if (entries_block_len <= x)
313 {
314 entries_block =
315 (char *) malloc (sizeof (char) * (entries_block_len + 1));
316 sprintf (entries_block, "---[ entries: %i ]---", entries % 1000);
317
318 entries_block_len = strlen (entries_block);
319 for (i = x - entries_block_len, j = 0; j < entries_block_len; i++, j++)
320 {
321 footer_str[i] = entries_block[j];
322 }
323
324 free (entries_block);
325 }
326
327 footer_str[x] = '\0';
328
329 wattron (win, A_REVERSE);
330 mvwprintw (win, LINES - 2, 0, footer_str);
331 wstandend (win);
332 free (footer_str);
333 }
334
335 /***************************************************************************
336 Display the index to the end-user.
337 */
338
339 void
display_index()340 display_index ()
341 {
342 redrawwin (win);
343 wrefresh (win);
344 }
345
346 /***************************************************************************
347 Handle input from the end-user.
348 */
349
350 int
process_index_commands()351 process_index_commands ()
352 {
353 bool done = FALSE;
354 int return_command = 0;
355 int ch = 0;
356
357 while (!done)
358 {
359 ch = wgetch (win);
360
361 switch (ch)
362 {
363 case KEY_NPAGE:
364 case ' ':
365 case KEY_C3:
366 menu_driver (menu, REQ_SCR_DPAGE);
367 break;
368 case '/':
369 index_search ();
370 break;
371 case 'a':
372 return_command = INDEX_COMMAND_ADD;
373 clear_message_bar (menu);
374 done = TRUE;
375 break;
376 case KEY_PPAGE:
377 case 'b':
378 case KEY_A3:
379 menu_driver (menu, REQ_SCR_UPAGE);
380 break;
381 case 'd':
382 return_command = INDEX_COMMAND_DELETE;
383 clear_message_bar (menu);
384 done = TRUE;
385 break;
386 case 'e':
387 return_command = INDEX_COMMAND_EDIT;
388 clear_message_bar (menu);
389 done = TRUE;
390 break;
391 case 'f':
392 filter_menu ();
393 break;
394 case 'F':
395 unfilter_menu ();
396 break;
397 case KEY_HOME:
398 case 'g':
399 case KEY_A1:
400 menu_driver (menu, REQ_FIRST_ITEM);
401 break;
402 case KEY_END:
403 case 'G':
404 case KEY_C1:
405 menu_driver (menu, REQ_LAST_ITEM);
406 break;
407 case 'h':
408 (*display_help) ();
409 touchwin (win);
410 wrefresh (win);
411 wrefresh (sub);
412 clear_message_bar (menu);
413 break;
414 case KEY_DOWN:
415 case 'j':
416 menu_driver (menu, REQ_NEXT_ITEM);
417 break;
418 case KEY_UP:
419 case 'k':
420 menu_driver (menu, REQ_PREV_ITEM);
421 break;
422 case 'n':
423 index_search_next ();
424 break;
425 case 'N':
426 index_search_prev ();
427 break;
428 case 'q':
429 clear_message_bar (menu);
430 return_command = INDEX_COMMAND_QUIT;
431 done = TRUE;
432 break;
433 case 's':
434 sort_ascending (SORT_ENTRIES_BY_FAMILY_N);
435 break;
436 case 'S':
437 sort_descending (SORT_ENTRIES_BY_FAMILY_N);
438 break;
439 case 't':
440 menu_driver (menu, REQ_TOGGLE_ITEM);
441 clear_message_bar (menu);
442 break;
443 case KEY_ENTER:
444 case 13:
445 case 'v':
446 return_command = INDEX_COMMAND_VIEW;
447 clear_message_bar (menu);
448 done = TRUE;
449 break;
450 case 'V':
451 return_command = INDEX_COMMAND_RAW_VIEW;
452 clear_message_bar (menu);
453 done = TRUE;
454 break;
455 default:
456 beep ();
457 break;
458 }
459 }
460 return return_command;
461 }
462
463 /***************************************************************************
464 */
465
466 void
select_next_item()467 select_next_item ()
468 {
469 menu_driver (menu, REQ_NEXT_ITEM);
470 }
471
472 /***************************************************************************
473 */
474
475 void
select_previous_item()476 select_previous_item ()
477 {
478 menu_driver (menu, REQ_PREV_ITEM);
479 }
480
481 /***************************************************************************
482 */
483
484 ITEM *
get_current_item()485 get_current_item ()
486 {
487 return current_item (menu);
488 }
489
490 /***************************************************************************
491 */
492
493 int
get_entry_number(const ITEM * item)494 get_entry_number (const ITEM * item)
495 {
496 return (1 + item_index (item));
497 }
498
499 /***************************************************************************
500 */
501
502 void
set_index_help_fcn(void (* fcn)(void))503 set_index_help_fcn (void (*fcn) (void))
504 {
505 display_help = fcn;
506 }
507
508 /***************************************************************************
509 */
510
511 static void
sort_ascending(int sort_by)512 sort_ascending (int sort_by)
513 {
514 int ch = 0;
515 wmove (win, LINES - 1, 0);
516 wclrtoeol (win);
517 wprintw (win,
518 "Sort ascending by [F]amily Name, [G]iven Name, [E]mail, [T]el: ");
519 ch = wgetch (win);
520
521 wmove (win, LINES - 1, 0);
522 wclrtoeol (win);
523
524 switch (tolower (ch))
525 {
526 case 'f':
527 g_sort_by = SORT_ENTRIES_BY_FAMILY_N;
528 break;
529 case 'g':
530 g_sort_by = SORT_ENTRIES_BY_GIVEN_N;
531 break;
532 case 'e':
533 g_sort_by = SORT_ENTRIES_BY_EMAIL;
534 break;
535 case 't':
536 g_sort_by = SORT_ENTRIES_BY_TEL;
537 break;
538 default:
539 return;
540 break;
541 }
542
543 finish_index ();
544 init_index (datafile);
545 display_index ();
546 }
547
548 /***************************************************************************
549 */
550
551 static void
sort_descending(int sort_by)552 sort_descending (int sort_by)
553 {
554 int ch = 0;
555 wmove (win, LINES - 1, 0);
556 wclrtoeol (win);
557 wprintw (win,
558 "Sort descending by [F]amily Name, [G]iven Name, [E]mail, [T]el: ");
559 ch = wgetch (win);
560
561 wmove (win, LINES - 1, 0);
562 wclrtoeol (win);
563
564 switch (tolower (ch))
565 {
566 case 'f':
567 g_sort_by = SORT_ENTRIES_BY_FAMILY_N;
568 break;
569 case 'g':
570 g_sort_by = SORT_ENTRIES_BY_GIVEN_N;
571 break;
572 case 'e':
573 g_sort_by = SORT_ENTRIES_BY_EMAIL;
574 break;
575 case 't':
576 g_sort_by = SORT_ENTRIES_BY_TEL;
577 break;
578 default:
579 return;
580 break;
581 }
582
583 finish_index ();
584 init_index (datafile);
585 display_index ();
586 }
587
588 /***************************************************************************
589 */
590
591 void
refresh_index()592 refresh_index ()
593 {
594 int count = 0;
595 int current_index = 0;
596 ITEM *item = NULL;
597 ITEM **items = NULL;
598
599 item = current_item (menu);
600
601 if (NULL == item)
602 {
603 finish_index ();
604 init_index (datafile);
605 display_index ();
606 return;
607 }
608
609 current_index = item_index (item);
610
611 finish_index ();
612 init_index (datafile);
613 display_index ();
614
615 count = item_count (menu);
616
617 items = menu_items (menu);
618 current_index = current_index >= count ? count : current_index;
619 set_current_item (menu, items[current_index]);
620 }
621
622 /***************************************************************************
623 */
624
625 static void
unfilter_menu()626 unfilter_menu ()
627 {
628 if (NULL != filter_string)
629 {
630 free (filter_string);
631 }
632
633 filter_string = NULL;
634
635 finish_index ();
636 init_index (datafile);
637 display_index ();
638 }
639
640 /***************************************************************************
641 */
642
643 static void
filter_menu()644 filter_menu ()
645 {
646 char f_string[80];
647 ITEM *found_item = NULL;
648
649 wmove (win, LINES - 1, 0);
650 wclrtoeol (win);
651 wprintw (win, "Filter: ");
652 nocbreak ();
653 echo ();
654 wscanw (win, "%s", f_string);
655
656 cbreak ();
657 noecho ();
658 wmove (win, LINES - 1, 0);
659 wclrtoeol (win);
660
661 if (NULL != filter_string)
662 {
663 free (filter_string);
664 }
665
666 filter_string = strdup (f_string);
667
668 finish_index ();
669 init_index (datafile);
670 display_index ();
671 }
672
673 /***************************************************************************
674 Search for the entered search string in the index index screen.
675 This will move the cursor and print a status message line at
676 the bottom of the screen.
677 */
678
679 static void
index_search()680 index_search ()
681 {
682 char search_string[80];
683
684 wmove (win, LINES - 1, 0);
685 wclrtoeol (win);
686 wprintw (win, "Search for: ");
687 nocbreak ();
688 echo ();
689 wscanw (win, "%s", search_string);
690 cbreak ();
691 noecho ();
692
693 set_menu_search_string (menu, search_string);
694 index_search_next ();
695 }
696
697 /***************************************************************************
698 */
699
700 static int
scroll_to_result(ITEM * found_item)701 scroll_to_result (ITEM * found_item)
702 {
703 int direction = 0;
704
705 if (NULL != found_item)
706 {
707 int current_index = -1;
708 int found_index = -1;
709
710 current_index = item_index (current_item (menu));
711 found_index = item_index (found_item);
712
713 direction = found_index > current_index ? REQ_SCR_DPAGE : REQ_SCR_UPAGE;
714
715 while (FALSE == item_visible (found_item))
716 {
717 menu_driver (menu, direction);
718 }
719
720 set_current_item (menu, found_item);
721 }
722
723 return direction;
724 }
725
726 /***************************************************************************
727 */
728
729 static void
clear_message_bar(MENU * m)730 clear_message_bar (MENU * m)
731 {
732 WINDOW *w = NULL;
733
734 w = menu_win (m);
735
736 wmove (w, LINES - 1, 0);
737 wclrtoeol (w);
738 }
739
740 /***************************************************************************
741 */
742
743 static void
index_search_next()744 index_search_next ()
745 {
746 ITEM *found_item = NULL;
747 int direction = 0;
748
749 found_item = search_menu (menu, REQ_SCR_DPAGE);
750 direction = scroll_to_result (found_item);
751 switch (direction)
752 {
753 case 0:
754 wmove (win, LINES - 1, 0);
755 wclrtoeol (win);
756 mvwprintw (win, LINES - 1, 0, "Not found.");
757 beep ();
758 break;
759
760 case REQ_SCR_UPAGE:
761 mvwprintw (win, LINES - 1, 0, "Search wrapped to top.");
762 break;
763
764 default:
765 break;
766 }
767 }
768
769 /***************************************************************************
770 */
771
772 static void
index_search_prev()773 index_search_prev ()
774 {
775 ITEM *found_item = NULL;
776 int direction = 0;
777
778 found_item = search_menu (menu, REQ_SCR_UPAGE);
779 direction = scroll_to_result (found_item);
780 switch (direction)
781 {
782 case 0:
783 wmove (win, LINES - 1, 0);
784 wclrtoeol (win);
785 mvwprintw (win, LINES - 1, 0, "Not found.");
786 beep ();
787 break;
788
789 case REQ_SCR_DPAGE:
790 mvwprintw (win, LINES - 1, 0, "Search wrapped to bottom.");
791 break;
792
793 default:
794 break;
795 }
796 }
797