]> git.deb.at Git - pkg/abook.git/blob - ui.c
Support for dynamic views.
[pkg/abook.git] / ui.c
1
2 /*
3  * $Id$
4  *
5  * by JH <jheinonen@users.sourceforge.net>
6  *
7  * Copyright (C) Jaakko Heinonen
8  */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <signal.h>
16 #include <ctype.h>
17 #include "abook.h"
18 #include <assert.h>
19 #include "ui.h"
20 #include "edit.h"
21 #include "database.h"
22 #include "gettext.h"
23 #include "list.h"
24 #include "misc.h"
25 #include "options.h"
26 #include "filter.h"
27 #include "xmalloc.h"
28 #ifdef HAVE_CONFIG_H
29 #       include "config.h"
30 #endif
31 #ifdef HAVE_SYS_IOCTL_H
32 #       include <sys/ioctl.h>
33 #endif
34
35 #include "abook_rl.h"
36
37 /*
38  * external variables
39  */
40
41 extern int curitem;
42 extern int items;
43 extern char *datafile;
44
45 extern bool alternative_datafile;
46
47 /*
48  * internal variables
49  */
50
51 static bool ui_initialized = FALSE;
52
53 static bool should_resize = FALSE;
54 static bool can_resize = FALSE;
55
56 static WINDOW *top = NULL, *bottom = NULL;
57
58
59 static void
60 init_windows()
61 {
62         top = newwin(LIST_TOP - 1, COLS, 0, 0);
63         bottom = newwin(LINES - LIST_BOTTOM, COLS, LIST_BOTTOM, 0);
64 }
65
66 static void
67 free_windows()
68 {
69         delwin(top);
70         delwin(bottom);
71 }
72
73
74 #ifdef SIGWINCH
75 static void
76 resize_abook()
77 {
78 #ifdef TIOCGWINSZ
79         struct winsize winsz;
80
81         ioctl(0, TIOCGWINSZ, &winsz);
82 #ifdef DEBUG
83         if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
84                 fprintf(stderr, "Warning: COLS=%d, LINES=%d\n", winsz.ws_col, winsz.ws_row);
85         }
86 #endif
87
88         if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
89 #ifdef HAVE_RESIZETERM
90                 resizeterm(winsz.ws_row, winsz.ws_col);
91 #else
92                 COLS = winsz.ws_col;
93                 LINES = winsz.ws_row;
94 #endif
95         }
96
97         should_resize = FALSE;
98         close_list(); /* we need to recreate windows */
99         init_list();
100         free_windows();
101         init_windows();
102         refresh_screen();
103         refresh();
104 #endif /* TIOCGWINSZ */
105 }
106
107
108 static void
109 win_changed(int i)
110 {
111         if(can_resize)
112                 resize_abook();
113         else
114                 should_resize = TRUE;
115 }
116 #endif /* SIGWINCH */
117
118
119 int
120 is_ui_initialized()
121 {
122         return ui_initialized;
123 }
124
125 void
126 ui_init_curses()
127 {
128         if(!is_ui_initialized())
129                 initscr();
130         cbreak();
131         noecho();
132         nonl();
133         intrflush(stdscr, FALSE);
134         keypad(stdscr, TRUE);
135 }
136
137 int
138 init_ui()
139 {
140         ui_init_curses();
141 #ifdef DEBUG
142         fprintf(stderr, "init_abook():\n");
143         fprintf(stderr, "  COLS = %d, LINES = %d\n", COLS, LINES);
144 #endif
145         if( LINES < MIN_LINES || COLS < MIN_COLS ) {
146                 clear(); refresh(); endwin();
147                 fprintf(stderr, _("Your terminal size is %dx%d\n"), COLS, LINES);
148                 fprintf(stderr, _("Terminal is too small. Minimum terminal "
149                                 "size for abook is "
150                                 "%dx%d\n"), MIN_COLS, MIN_LINES);
151                 return 1;
152         }
153
154         init_list();
155         init_windows();
156
157         ui_initialized = TRUE;
158
159 #ifdef SIGWINCH
160         signal(SIGWINCH, win_changed);
161 #endif
162
163         return 0;
164 }
165
166 void
167 close_ui()
168 {
169         close_list();
170         free_windows();
171         clear();
172         refresh();
173         endwin();
174
175         ui_initialized = FALSE;
176 }
177
178
179 void
180 headerline(const char *str)
181 {
182         werase(top);
183
184         mvwhline(top, 1, 0, UI_HLINE_CHAR, COLS);
185
186         mvwprintw(top, 0, 0, "%s | %s", PACKAGE " " VERSION, str);
187
188         refresh();
189         wrefresh(top);
190 }
191
192
193 void
194 refresh_screen()
195 {
196 #ifdef SIGWINCH
197         if(should_resize) {
198                 resize_abook();
199                 return;
200         }
201 #endif
202         clear();
203
204         refresh_statusline();
205         headerline(gettext(MAIN_HELPLINE));
206         list_headerline();
207
208         refresh_list();
209 }
210
211
212 int
213 statusline_msg(const char *msg)
214 {
215         int c;
216
217         clear_statusline();
218         statusline_addstr(msg);
219         c = getch();
220 #ifdef DEBUG
221         fprintf(stderr, "statusline_msg(\"%s\")\n", msg);
222 #endif
223         clear_statusline();
224
225         return c;
226 }
227
228 void
229 statusline_addstr(const char *str)
230 {
231         mvwaddstr(bottom, 1, 0, str);
232         refresh();
233         wrefresh(bottom);
234 }
235
236 /* Same as statusline_addstr(), but hilight "<str>" sequences if the terminal
237  * supports it */
238 static void
239 statusline_addhlstr(const char *str)
240 {
241 #if defined(A_BOLD) && defined(A_NORMAL) && defined(A_DIM)
242         const char *p = str, *start = str;
243         char *tmp;
244         int pos = 0;
245
246         while(1) {
247                 if(!*p || strchr("<>", *p)) {
248                         if(p - start > 0) {
249                                 wattrset(bottom, (*p == '>') ? A_BOLD : A_NORMAL);
250                                 tmp = xstrndup(start, p - start);
251                                 mvwaddstr(bottom, 1, pos, tmp);
252                                 pos += strwidth(tmp);
253                                 free(tmp);
254                         }
255                         if(*p) {
256                                 start = p + 1;
257
258                                 /* show tag markers */
259                                 wattrset(bottom, A_DIM);
260                                 mvwaddch(bottom, 1, pos++, *p);
261                         }
262                 }
263
264                 if(!*p) {
265                         wattrset(bottom, A_NORMAL);
266                         break;
267                 }
268
269                 p++;
270         }
271 #else
272         mvwaddstr(bottom, 1, 0, str);
273 #endif
274
275         refresh();
276         wrefresh(bottom);
277 }
278
279 int
280 statusline_askchoice(const char *msg, const char *choices, short dflt)
281 {
282         char *s;
283         int ch;
284
285         assert((dflt >= 0) && (dflt <= strlen(choices)));
286
287         if(dflt) {
288                 s = strdup_printf("%s [%c]", msg, choices[dflt - 1]);
289                 statusline_addhlstr(s);
290                 free(s);
291         } else
292                 statusline_addhlstr(msg);
293
294         while(1)
295         {
296                 ch = tolower(getch());
297
298                 if(ch == 7) /* ctrl+G */
299                         return 0;
300
301                 if(dflt && (ch == '\r')) /* default choice */
302                         return dflt;
303
304                 if((s = strchr(choices, ch)))
305                         return (s - choices + 1);
306         }
307 }
308
309 char *
310 ui_readline(char *prompt, char *s, size_t limit, bool use_completion)
311 {
312         int y, x;
313         char *ret;
314
315         mvwaddstr(bottom, 1, 0, prompt);
316
317         getyx(bottom, y, x);
318
319         ret = abook_readline(bottom, y, x, s, use_completion);
320
321         if(ret) {
322                 strtrim(ret);
323                 if(strlen(ret) > limit && limit > 0)
324                         ret[limit] = '\0';
325         }
326
327         return ret;
328 }
329
330 int
331 statusline_ask_boolean(char *msg, int def)
332 {
333         int ret;
334         char *msg2 = strconcat(msg,  def ? _(" (Y/n)?") : _(" (y/N)?"), NULL);
335         char ch;
336
337         statusline_addstr(msg2);
338
339         free(msg2);
340
341         ch = tolower(getch());
342
343         if(ch == *(S_("keybinding for no|n")))
344                 ret = FALSE;
345         else if(ch == *(S_("keybinding for yes|y")))
346                 ret = TRUE;
347         else
348                 ret = def;
349
350         clear_statusline();
351
352         return ret;
353 }
354
355 void
356 refresh_statusline()
357 {
358         werase(bottom);
359
360         mvwhline(bottom, 0, 0, UI_HLINE_CHAR, COLS);
361
362         refresh();
363         wrefresh(bottom);
364 }
365
366 char *
367 ask_filename(char *prompt)
368 {
369         char *buf = NULL;
370
371         clear_statusline();
372
373         buf = ui_readline(prompt, NULL, -1, 1);
374
375         return buf;
376 }
377
378 void
379 clear_statusline()
380 {
381         wmove(bottom, 1, 0);
382         wclrtoeol(bottom);
383         wrefresh(bottom);
384         refresh();
385 }
386
387 /*
388  * help
389  */
390
391 #include "help.h"
392
393 void
394 display_help(int help)
395 {
396         int i;
397         char **tbl;
398         WINDOW *helpw;
399
400         switch(help) {
401                 case HELP_MAIN:
402                         tbl = mainhelp;
403                         break;
404                 case HELP_EDITOR:
405                         tbl = editorhelp;
406                         break;
407                 default:return;
408         }
409
410         helpw = newwin(LINES - 5, COLS - 6, 2, 3);
411         erase();
412         headerline(_("help"));
413
414         for(i = 0; tbl[i] != NULL; i++) {
415                 waddstr(helpw, gettext(tbl[i]));
416                 if( (!((i + 1) % (LINES - 8))) ||
417                         (tbl[i + 1] == NULL) ) {
418                         refresh();
419                         wrefresh(helpw);
420                         refresh_statusline();
421                         if(statusline_msg(_("Press any key to continue..."))
422                                         == 'q')
423                                 break;
424                         wclear(helpw);
425                 }
426         }
427
428         clear_statusline();
429         delwin(helpw);
430 }
431
432 /*
433  * end of help
434  */
435
436 extern char *selected;
437 extern int curitem;
438
439 void
440 get_commands()
441 {
442         int ch;
443
444         for(;;) {
445                 can_resize = TRUE; /* it's safe to resize now */
446                 if(!opt_get_bool(BOOL_SHOW_CURSOR))
447                         hide_cursor();
448                 if(should_resize)
449                         refresh_screen();
450                 ch = getch();
451                 if(!opt_get_bool(BOOL_SHOW_CURSOR))
452                         show_cursor();
453                 can_resize = FALSE; /* it's not safe to resize anymore */
454                 switch(ch) {
455                         case 'q': return;
456                         case 'Q': quit_abook(QUIT_DONTSAVE);    break;
457                         case 'P': print_stderr(selected_items() ?
458                                                   -1 : list_current_item());
459                                   return;
460                         case '?':
461                                   display_help(HELP_MAIN);
462                                   refresh_screen();
463                                   break;
464                         case 'a': add_item();           break;
465                         case '\r': edit_item(-1);       break;
466                         case KEY_DC:
467                         case 'd':
468                         case 'r': ui_remove_items();    break;
469                         case 'D': duplicate_item();     break;
470                         case 12: refresh_screen();      break;
471
472                         case 'k':
473                         case KEY_UP: scroll_up();       break;
474                         case 'j':
475                         case KEY_DOWN: scroll_down();   break;
476                         case 'K':
477                         case KEY_PPAGE: page_up();      break;
478                         case 'J':
479                         case KEY_NPAGE: page_down();    break;
480
481                         case 'g':
482                         case KEY_HOME: goto_home();     break;
483                         case 'G':
484                         case KEY_END: goto_end();       break;
485
486                         case 'w': save_database();
487                                   break;
488                         case 'l': ui_read_database();   break;
489                         case 'i': import_database();    break;
490                         case 'e': export_database();    break;
491                         case 'C': ui_clear_database();  break;
492
493                         case 'o': ui_open_datafile();   break;
494
495                         case 's': sort_by_field("name");break;
496                         case 'S': sort_surname();       break;
497                         case 'F': sort_by_field(NULL);  break;
498
499                         case '/': ui_find(0);           break;
500                         case '\\': ui_find(1);          break;
501
502                         case ' ': if(curitem >= 0) {
503                                    selected[curitem] = !selected[curitem];
504                                    ui_print_number_of_items();
505                                    refresh_list();
506                                   }
507                                 break;
508                         case '+': select_all();
509                                   refresh_list();
510                                 break;
511                         case '-': select_none();
512                                   refresh_list();
513                                 break;
514                         case '*': invert_selection();
515                                   refresh_list();
516                                  break;
517                         case 'A': move_curitem(MOVE_ITEM_UP);
518                                 break;
519                         case 'Z': move_curitem(MOVE_ITEM_DOWN);
520                                 break;
521
522                         case 'm': launch_mutt(selected_items() ?
523                                                   -1 : list_current_item());
524                                   refresh_screen();
525                                   break;
526
527                         case 'p': ui_print_database(); break;
528
529                         case 'v': launch_wwwbrowser(list_current_item());
530                                   refresh_screen();
531                                   break;
532                 }
533         }
534 }
535
536 void
537 ui_remove_items()
538 {
539         if(list_is_empty())
540                 return;
541
542         if(statusline_ask_boolean(_("Remove selected item(s)"), TRUE))
543                 remove_selected_items();
544
545         clear_statusline();
546         refresh_list();
547 }
548
549 void
550 ui_clear_database()
551 {
552         if(statusline_ask_boolean(_("Clear WHOLE database"), FALSE)) {
553                 close_database();
554                 refresh_list();
555         }
556 }
557
558 void
559 ui_find(int next)
560 {
561         int item = -1;
562         static char findstr[MAX_FIELD_LEN];
563         int search_fields[] = {NAME, EMAIL, NICK, -1};
564
565         clear_statusline();
566
567         if(next) {
568                 if(!*findstr)
569                         return;
570         } else {
571                 char *s;
572                 s = ui_readline("/", findstr, MAX_FIELD_LEN - 1, 0);
573                 strncpy(findstr, s, MAX_FIELD_LEN);
574                 free(s);
575                 refresh_screen();
576         }
577
578         if( (item = find_item(findstr, curitem + !!next, search_fields)) < 0 &&
579                         (item = find_item(findstr, 0, search_fields)) >= 0)
580                 statusline_addstr(_("Search hit bottom, continuing at top"));
581
582         if(item >= 0) {
583                 curitem = item;
584                 refresh_list();
585         }
586 }
587
588 void
589 ui_print_number_of_items()
590 {
591         char *str = strdup_printf("     " "|%3d/%3d", selected_items(), items);
592
593         mvaddstr(0, COLS-strlen(str), str);
594
595         free(str);
596 }
597
598 void
599 ui_read_database()
600 {
601         char *msg;
602
603         if(items > 0) {
604                 msg = strdup_printf(_("Your current data will be lost - "
605                                 "Press '%c' to continue"),
606                                 *(S_("keybinding for yes|y")));
607                 if(!statusline_ask_boolean(msg, FALSE)) {
608                         free(msg);
609                         return;
610                 }
611                 free(msg);
612         }
613
614         load_database(datafile);
615         refresh_list();
616 }
617
618
619 void
620 ui_print_database()
621 {
622         FILE *handle;
623         char *command = opt_get_str(STR_PRINT_COMMAND);
624         int mode;
625
626         if(list_is_empty())
627                 return;
628
629         switch(statusline_askchoice(_("Print <a>ll, print <s>elected, or <c>ancel?"), S_("keybindings:all/selected/cancel|asc"), 3)) {
630                 case 1:
631                         mode = ENUM_ALL;
632                         break;
633                 case 2:
634                         if( !selected_items() ) {
635                                 statusline_msg(_("No selected items"));
636                                 return;
637                         }
638                         mode = ENUM_SELECTED;
639                         break;
640                 default:
641                         refresh_screen();
642                         return;
643         }
644
645         clear_statusline();
646
647         if( ! *command || (handle = popen(command, "w")) == NULL)
648                 return;
649
650         fexport("text", handle, mode);
651
652         pclose(handle);
653 }
654
655
656 void
657 ui_open_datafile()
658 {
659         char *filename;
660
661         filename = ask_filename(_("File to open: "));
662
663         if(!filename || ! *filename) {
664                 free(filename);
665                 refresh_screen();
666                 return;
667         }
668
669         if(opt_get_bool(BOOL_AUTOSAVE))
670                 save_database();
671         else if(statusline_ask_boolean(_("Save current database"), FALSE))
672                 save_database();
673
674         close_database();
675
676         load_database(filename);
677
678         if(items == 0) {
679                 statusline_msg(_("Sorry, the specified file appears not to be a valid abook addressbook"));
680                 load_database(datafile);
681         } else {
682                 free(datafile);
683                 datafile = xstrdup(filename);
684         }
685
686         refresh_screen();
687         free(filename);
688
689         alternative_datafile = TRUE;
690 }