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