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