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