]> git.deb.at Git - pkg/abook.git/blob - ui.c
Add basic color support
[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 #include "color.h"
29 #ifdef HAVE_CONFIG_H
30 #       include "config.h"
31 #endif
32 #ifdef HAVE_SYS_IOCTL_H
33 #       include <sys/ioctl.h>
34 #endif
35
36 #include "abook_rl.h"
37
38 /*
39  * external variables
40  */
41
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         if(opt_get_bool(BOOL_USE_COLORS)) {
135                 start_color();
136                 use_default_colors();
137                 ui_init_color_pairs_user();
138         }
139 }
140
141
142 #define CHECK_COLOR_NAME(value, name, DEFNAME) \
143         if(!strcmp((name), (value))){ \
144                 return DEFNAME; \
145         }
146 short
147 opt_color_to_color(enum str_opts enum_name)
148 {
149         char* name = opt_get_str(enum_name);
150         CHECK_COLOR_NAME(name, "default", COLOR_DEFAULT)
151         else CHECK_COLOR_NAME(name, "black", COLOR_BLACK)
152         else CHECK_COLOR_NAME(name, "red", COLOR_RED)
153         else CHECK_COLOR_NAME(name, "green", COLOR_GREEN)
154         else CHECK_COLOR_NAME(name, "yellow", COLOR_YELLOW)
155         else CHECK_COLOR_NAME(name, "blue", COLOR_BLUE)
156         else CHECK_COLOR_NAME(name, "magenta", COLOR_MAGENTA)
157         else CHECK_COLOR_NAME(name, "cyan", COLOR_CYAN)
158         else CHECK_COLOR_NAME(name, "white", COLOR_WHITE)
159     else return COLOR_DEFAULT;
160 }
161
162 void
163 ui_init_color_pairs_user()
164 {
165         init_pair(CP_HEADER, opt_color_to_color(STR_COLOR_HEADER_FG),
166                              opt_color_to_color(STR_COLOR_HEADER_BG));
167         init_pair(CP_FOOTER, opt_color_to_color(STR_COLOR_FOOTER_FG),
168                              opt_color_to_color(STR_COLOR_FOOTER_BG));
169         init_pair(CP_LIST_EVEN, opt_color_to_color(STR_COLOR_LIST_EVEN_FG),
170                                 opt_color_to_color(STR_COLOR_LIST_EVEN_BG));
171         init_pair(CP_LIST_ODD,  opt_color_to_color(STR_COLOR_LIST_ODD_FG),
172                                 opt_color_to_color(STR_COLOR_LIST_ODD_BG));
173         init_pair(CP_LIST_HEADER, opt_color_to_color(STR_COLOR_LIST_HEADER_FG),
174                              opt_color_to_color(STR_COLOR_LIST_HEADER_BG));
175         init_pair(CP_LIST_HIGHLIGHT, opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_FG),
176                              opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_BG));
177         init_pair(CP_TAB_BORDER, opt_color_to_color(STR_COLOR_TAB_BORDER_FG),
178                              opt_color_to_color(STR_COLOR_TAB_BORDER_BG));
179         init_pair(CP_TAB_LABEL, opt_color_to_color(STR_COLOR_TAB_LABEL_FG),
180                              opt_color_to_color(STR_COLOR_TAB_LABEL_BG));
181         init_pair(CP_FIELD_NAME, opt_color_to_color(STR_COLOR_FIELD_NAME_FG),
182                              opt_color_to_color(STR_COLOR_FIELD_NAME_BG));
183         init_pair(CP_FIELD_VALUE, opt_color_to_color(STR_COLOR_FIELD_VALUE_FG),
184                              opt_color_to_color(STR_COLOR_FIELD_VALUE_BG));
185 }
186
187 int
188 init_ui()
189 {
190         ui_init_curses();
191 #ifdef DEBUG
192         fprintf(stderr, "init_abook():\n");
193         fprintf(stderr, "  COLS = %d, LINES = %d\n", COLS, LINES);
194 #endif
195         if( LINES < MIN_LINES || COLS < MIN_COLS ) {
196                 clear(); refresh(); endwin();
197                 fprintf(stderr, _("Your terminal size is %dx%d\n"), COLS, LINES);
198                 fprintf(stderr, _("Terminal is too small. Minimum terminal "
199                                 "size for abook is "
200                                 "%dx%d\n"), MIN_COLS, MIN_LINES);
201                 return 1;
202         }
203
204         init_list();
205         init_windows();
206
207         ui_initialized = TRUE;
208
209 #ifdef SIGWINCH
210         signal(SIGWINCH, win_changed);
211 #endif
212
213         return 0;
214 }
215
216 void
217 close_ui()
218 {
219         close_list();
220         free_windows();
221         clear();
222         refresh();
223         endwin();
224
225         ui_initialized = FALSE;
226 }
227
228
229 void
230 headerline(const char *str)
231 {
232         werase(top);
233
234         wattrset(top, COLOR_PAIR(CP_HEADER));
235         mvwhline(top, 0, 0, ' ', COLS);
236         mvwhline(top, 1, 0, UI_HLINE_CHAR, COLS);
237
238         mvwprintw(top, 0, 0, "%s | %s", PACKAGE " " VERSION, str);
239
240         refresh();
241         wrefresh(top);
242 }
243
244
245 void
246 refresh_screen()
247 {
248 #ifdef SIGWINCH
249         if(should_resize) {
250                 resize_abook();
251                 return;
252         }
253 #endif
254         clear();
255
256         refresh_statusline();
257         headerline(gettext(MAIN_HELPLINE));
258         list_headerline();
259
260         refresh_list();
261 }
262
263
264 int
265 statusline_msg(const char *msg)
266 {
267         int c;
268
269         clear_statusline();
270         statusline_addstr(msg);
271         c = getch();
272 #ifdef DEBUG
273         fprintf(stderr, "statusline_msg(\"%s\")\n", msg);
274 #endif
275         clear_statusline();
276
277         return c;
278 }
279
280 void
281 statusline_addstr(const char *str)
282 {
283         mvwaddstr(bottom, 1, 0, str);
284         refresh();
285         wrefresh(bottom);
286 }
287
288 /* Same as statusline_addstr(), but hilight "<str>" sequences if the terminal
289  * supports it */
290 static void
291 statusline_addhlstr(const char *str)
292 {
293 #if defined(A_BOLD) && defined(A_NORMAL) && defined(A_DIM)
294         const char *p = str, *start = str;
295         char *tmp;
296         int pos = 0;
297
298         while(1) {
299                 if(!*p || strchr("<>", *p)) {
300                         if(p - start > 0) {
301                                 wattrset(bottom, (*p == '>') ? A_BOLD : A_NORMAL);
302                                 tmp = xstrndup(start, p - start);
303                                 mvwaddstr(bottom, 1, pos, tmp);
304                                 pos += strwidth(tmp);
305                                 free(tmp);
306                         }
307                         if(*p) {
308                                 start = p + 1;
309
310                                 /* show tag markers */
311                                 wattrset(bottom, A_DIM);
312                                 mvwaddch(bottom, 1, pos++, *p);
313                         }
314                 }
315
316                 if(!*p) {
317                         wattrset(bottom, A_NORMAL);
318                         break;
319                 }
320
321                 p++;
322         }
323 #else
324         mvwaddstr(bottom, 1, 0, str);
325 #endif
326
327         refresh();
328         wrefresh(bottom);
329 }
330
331 int
332 statusline_askchoice(const char *msg, const char *choices, short dflt)
333 {
334         char *s;
335         int ch;
336
337         assert((dflt >= 0) && (dflt <= strlen(choices)));
338
339         if(dflt) {
340                 s = strdup_printf("%s [%c]", msg, choices[dflt - 1]);
341                 statusline_addhlstr(s);
342                 free(s);
343         } else
344                 statusline_addhlstr(msg);
345
346         while(1)
347         {
348                 ch = tolower(getch());
349
350                 if(ch == 7) /* ctrl+G */
351                         return 0;
352
353                 if(dflt && (ch == '\r')) /* default choice */
354                         return dflt;
355
356                 if((s = strchr(choices, ch)))
357                         return (s - choices + 1);
358         }
359 }
360
361 char *
362 ui_readline(const char *prompt, char *s, size_t limit, bool use_completion)
363 {
364         int y, x;
365         char *ret;
366
367         mvwaddstr(bottom, 1, 0, prompt);
368
369         getyx(bottom, y, x);
370
371         ret = abook_readline(bottom, y, x, s, use_completion);
372
373         if(ret) {
374                 strtrim(ret);
375                 if(strlen(ret) > limit && limit > 0)
376                         ret[limit] = '\0';
377         }
378
379         return ret;
380 }
381
382 int
383 statusline_ask_boolean(const char *msg, int def)
384 {
385         int ret;
386         char *msg2 = strconcat(msg,  def ? _(" (Y/n)?") : _(" (y/N)?"), NULL);
387         char ch;
388
389         statusline_addstr(msg2);
390
391         free(msg2);
392
393         ch = tolower(getch());
394
395         if(ch == *(S_("keybinding for no|n")))
396                 ret = FALSE;
397         else if(ch == *(S_("keybinding for yes|y")))
398                 ret = TRUE;
399         else
400                 ret = def;
401
402         clear_statusline();
403
404         return ret;
405 }
406
407 void
408 refresh_statusline()
409 {
410         werase(bottom);
411
412         wattrset(bottom, COLOR_PAIR(CP_FOOTER));
413         mvwhline(bottom, 0, 0, UI_HLINE_CHAR, COLS);
414
415         refresh();
416         wrefresh(bottom);
417 }
418
419 char *
420 ask_filename(const char *prompt)
421 {
422         char *buf = NULL;
423
424         clear_statusline();
425
426         buf = ui_readline(prompt, NULL, -1, 1);
427
428         return buf;
429 }
430
431 void
432 clear_statusline()
433 {
434         wmove(bottom, 1, 0);
435         wclrtoeol(bottom);
436         wrefresh(bottom);
437         refresh();
438 }
439
440 /*
441  * help
442  */
443
444 #include "help.h"
445
446 void
447 display_help(int help)
448 {
449         int i;
450         char **tbl;
451         WINDOW *helpw;
452
453         switch(help) {
454                 case HELP_MAIN:
455                         tbl = mainhelp;
456                         break;
457                 case HELP_EDITOR:
458                         tbl = editorhelp;
459                         break;
460                 default:return;
461         }
462
463         helpw = newwin(LINES - 5, COLS - 6, 2, 3);
464         erase();
465         headerline(_("help"));
466
467         for(i = 0; tbl[i] != NULL; i++) {
468                 waddstr(helpw, gettext(tbl[i]));
469                 if( (!((i + 1) % (LINES - 8))) ||
470                         (tbl[i + 1] == NULL) ) {
471                         refresh();
472                         wrefresh(helpw);
473                         refresh_statusline();
474                         if(statusline_msg(_("Press any key to continue..."))
475                                         == 'q')
476                                 break;
477                         wclear(helpw);
478                 }
479         }
480
481         clear_statusline();
482         delwin(helpw);
483 }
484
485 /*
486  * end of help
487  */
488
489 extern char *selected;
490
491 void
492 get_commands()
493 {
494         int ch;
495
496         for(;;) {
497                 can_resize = TRUE; /* it's safe to resize now */
498                 if(!opt_get_bool(BOOL_SHOW_CURSOR))
499                         hide_cursor();
500                 if(should_resize)
501                         refresh_screen();
502                 ch = getch();
503                 if(!opt_get_bool(BOOL_SHOW_CURSOR))
504                         show_cursor();
505                 can_resize = FALSE; /* it's not safe to resize anymore */
506                 switch(ch) {
507                         case 'q': return;
508                         case 'Q': quit_abook(QUIT_DONTSAVE);    break;
509                         case 'P': print_stderr(selected_items() ?
510                                                   -1 : list_get_curitem());
511                                   return;
512                         case '?':
513                                   display_help(HELP_MAIN);
514                                   refresh_screen();
515                                   break;
516                         case 'a': add_item();           break;
517                         case '\r': edit_item(-1);       break;
518                         case KEY_DC:
519                         case 'd':
520                         case 'r': ui_remove_items();    break;
521                         case 'D': duplicate_item();     break;
522                         case 12: refresh_screen();      break;
523
524                         case 'k':
525                         case KEY_UP: scroll_up();       break;
526                         case 'j':
527                         case KEY_DOWN: scroll_down();   break;
528                         case 'K':
529                         case KEY_PPAGE: page_up();      break;
530                         case 'J':
531                         case KEY_NPAGE: page_down();    break;
532
533                         case 'g':
534                         case KEY_HOME: goto_home();     break;
535                         case 'G':
536                         case KEY_END: goto_end();       break;
537
538                         case 'w': save_database();
539                                   break;
540                         case 'l': ui_read_database();   break;
541                         case 'i': import_database();    break;
542                         case 'e': export_database();    break;
543                         case 'C': ui_clear_database();  break;
544
545                         case 'o': ui_open_datafile();   break;
546
547                         case 's': sort_by_field("name");break;
548                         case 'S': sort_surname();       break;
549                         case 'F': sort_by_field(NULL);  break;
550
551                         case '/': ui_find(0);           break;
552                         case '\\': ui_find(1);          break;
553
554                         case ' ': if(list_get_curitem() >= 0) {
555                                    list_invert_curitem_selection();
556                                    ui_print_number_of_items();
557                                    refresh_list();
558                                   }
559                                 break;
560                         case '+': select_all();
561                                   refresh_list();
562                                 break;
563                         case '-': select_none();
564                                   refresh_list();
565                                 break;
566                         case '*': invert_selection();
567                                   refresh_list();
568                                  break;
569                         case 'A': move_curitem(MOVE_ITEM_UP);
570                                 break;
571                         case 'Z': move_curitem(MOVE_ITEM_DOWN);
572                                 break;
573
574                         case 'm': launch_mutt(selected_items() ?
575                                                   -1 : list_get_curitem());
576                                   refresh_screen();
577                                   break;
578
579                         case 'p': ui_print_database(); break;
580
581                         case 'v': launch_wwwbrowser(list_get_curitem());
582                                   refresh_screen();
583                                   break;
584                 }
585         }
586 }
587
588 void
589 ui_remove_items()
590 {
591         if(list_is_empty())
592                 return;
593
594         if(statusline_ask_boolean(_("Remove selected item(s)"), TRUE))
595                 remove_selected_items();
596
597         clear_statusline();
598         refresh_list();
599 }
600
601 void
602 ui_clear_database()
603 {
604         if(statusline_ask_boolean(_("Clear WHOLE database"), FALSE)) {
605                 close_database();
606                 refresh_list();
607         }
608 }
609
610 void
611 ui_find(int next)
612 {
613         int item = -1;
614         static char findstr[MAX_FIELD_LEN];
615         int search_fields[] = {NAME, EMAIL, NICK, -1};
616
617         clear_statusline();
618
619         if(next) {
620                 if(!*findstr)
621                         return;
622         } else {
623                 char *s;
624                 s = ui_readline("/", findstr, MAX_FIELD_LEN - 1, 0);
625                 refresh_screen();
626                 if(s == NULL) {
627                         return; /* user cancelled (ctrl-G) */
628                 } else {
629                         strncpy(findstr, s, MAX_FIELD_LEN);
630                         free(s);
631                 }
632         }
633
634         if( (item = find_item(findstr, list_get_curitem() + !!next,
635                         search_fields)) < 0 &&
636                         (item = find_item(findstr, 0, search_fields)) >= 0)
637                 statusline_addstr(_("Search hit bottom, continuing at top"));
638
639         if(item >= 0) {
640                 list_set_curitem(item);
641                 refresh_list();
642         }
643 }
644
645 void
646 ui_print_number_of_items()
647 {
648         char *str = strdup_printf("     " "|%3d/%3d",
649                 selected_items(), db_n_items());
650
651         attrset(COLOR_PAIR(CP_HEADER));
652         mvaddstr(0, COLS-strlen(str), str);
653
654         free(str);
655 }
656
657 void
658 ui_read_database()
659 {
660         char *msg;
661
662         if(!list_is_empty()) {
663                 msg = strdup_printf(_("Your current data will be lost - "
664                                 "Press '%c' to continue"),
665                                 *(S_("keybinding for yes|y")));
666                 if(!statusline_ask_boolean(msg, FALSE)) {
667                         free(msg);
668                         return;
669                 }
670                 free(msg);
671         }
672
673         load_database(datafile);
674         refresh_list();
675 }
676
677
678 void
679 ui_print_database()
680 {
681         FILE *handle;
682         char *command = opt_get_str(STR_PRINT_COMMAND);
683         int mode;
684
685         if(list_is_empty())
686                 return;
687
688         switch(statusline_askchoice(_("Print <a>ll, print <s>elected, or <c>ancel?"), S_("keybindings:all/selected/cancel|asc"), 3)) {
689                 case 1:
690                         mode = ENUM_ALL;
691                         break;
692                 case 2:
693                         if( !selected_items() ) {
694                                 statusline_msg(_("No selected items"));
695                                 return;
696                         }
697                         mode = ENUM_SELECTED;
698                         break;
699                 default:
700                         refresh_screen();
701                         return;
702         }
703
704         clear_statusline();
705
706         if( ! *command || (handle = popen(command, "w")) == NULL)
707                 return;
708
709         fexport("text", handle, mode);
710
711         pclose(handle);
712 }
713
714
715 void
716 ui_open_datafile()
717 {
718         char *filename;
719
720         filename = ask_filename(_("File to open: "));
721
722         if(!filename || ! *filename) {
723                 free(filename);
724                 refresh_screen();
725                 return;
726         }
727
728         if(opt_get_bool(BOOL_AUTOSAVE))
729                 save_database();
730         else if(statusline_ask_boolean(_("Save current database"), FALSE))
731                 save_database();
732
733         close_database();
734
735         load_database(filename);
736
737         if(list_is_empty()) {
738                 statusline_msg(_("Sorry, the specified file appears not to be a valid abook addressbook"));
739                 load_database(datafile);
740         } else {
741                 free(datafile);
742                 datafile = xstrdup(filename);
743         }
744
745         refresh_screen();
746         free(filename);
747
748         alternative_datafile = TRUE;
749 }