5 * by JH <jheinonen@users.sourceforge.net>
7 * Copyright (C) Jaakko Heinonen
33 #ifdef HAVE_SYS_IOCTL_H
34 # include <sys/ioctl.h>
43 extern char *datafile;
45 extern bool alternative_datafile;
51 static bool ui_initialized = FALSE;
53 static bool should_resize = FALSE;
54 static bool can_resize = FALSE;
55 static struct timeval last_click_time;
56 static int double_click_interval = 200; /* maximum time in milliseconds */
58 static WINDOW *top = NULL, *bottom = NULL;
64 top = newwin(LIST_TOP - 1, COLS, 0, 0);
65 bottom = newwin(LINES - LIST_BOTTOM, COLS, LIST_BOTTOM, 0);
83 ioctl(0, TIOCGWINSZ, &winsz);
85 if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
86 fprintf(stderr, "Warning: COLS=%d, LINES=%d\n", winsz.ws_col, winsz.ws_row);
90 if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
91 #ifdef HAVE_RESIZETERM
92 resizeterm(winsz.ws_row, winsz.ws_col);
99 should_resize = FALSE;
100 close_list(); /* we need to recreate windows */
106 #endif /* TIOCGWINSZ */
116 should_resize = TRUE;
118 #endif /* SIGWINCH */
124 return ui_initialized;
130 if(!is_ui_initialized())
135 intrflush(stdscr, FALSE);
136 if(opt_get_bool(BOOL_USE_MOUSE)) {
138 timerclear(&last_click_time);
139 ui_enable_mouse(TRUE);
141 keypad(stdscr, TRUE);
142 if(opt_get_bool(BOOL_USE_COLORS)) {
144 use_default_colors();
145 ui_init_color_pairs_user();
150 ui_enable_mouse(bool enabled)
154 mask = BUTTON1_CLICKED | BUTTON4_PRESSED;
155 #if NCURSES_MOUSE_VERSION == 2
156 mask |= BUTTON5_PRESSED;
161 mousemask(mask, NULL);
164 /** Check the time elapsed since last click and tell if it should be
165 * interpreted as a double click
169 struct timeval click_time, click_diff, maxdiff;
170 maxdiff.tv_sec = double_click_interval / 1000;
171 maxdiff.tv_usec = (double_click_interval % 1000)*1000;
172 gettimeofday(&click_time, NULL);
174 timersub(&click_time, &last_click_time, &click_diff);
175 last_click_time = click_time;
176 return !timercmp(&click_diff, &maxdiff, >);
179 #define CHECK_COLOR_NAME(value, name, DEFNAME) \
180 if(!strcmp((name), (value))){ \
184 opt_color_to_color(enum str_opts enum_name)
186 char* name = opt_get_str(enum_name);
187 CHECK_COLOR_NAME(name, "default", COLOR_DEFAULT)
188 else CHECK_COLOR_NAME(name, "black", COLOR_BLACK)
189 else CHECK_COLOR_NAME(name, "red", COLOR_RED)
190 else CHECK_COLOR_NAME(name, "green", COLOR_GREEN)
191 else CHECK_COLOR_NAME(name, "yellow", COLOR_YELLOW)
192 else CHECK_COLOR_NAME(name, "blue", COLOR_BLUE)
193 else CHECK_COLOR_NAME(name, "magenta", COLOR_MAGENTA)
194 else CHECK_COLOR_NAME(name, "cyan", COLOR_CYAN)
195 else CHECK_COLOR_NAME(name, "white", COLOR_WHITE)
196 else return COLOR_DEFAULT;
200 ui_init_color_pairs_user()
202 init_pair(CP_HEADER, opt_color_to_color(STR_COLOR_HEADER_FG),
203 opt_color_to_color(STR_COLOR_HEADER_BG));
204 init_pair(CP_FOOTER, opt_color_to_color(STR_COLOR_FOOTER_FG),
205 opt_color_to_color(STR_COLOR_FOOTER_BG));
206 init_pair(CP_LIST_EVEN, opt_color_to_color(STR_COLOR_LIST_EVEN_FG),
207 opt_color_to_color(STR_COLOR_LIST_EVEN_BG));
208 init_pair(CP_LIST_ODD, opt_color_to_color(STR_COLOR_LIST_ODD_FG),
209 opt_color_to_color(STR_COLOR_LIST_ODD_BG));
210 init_pair(CP_LIST_HEADER, opt_color_to_color(STR_COLOR_LIST_HEADER_FG),
211 opt_color_to_color(STR_COLOR_LIST_HEADER_BG));
212 init_pair(CP_LIST_HIGHLIGHT, opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_FG),
213 opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_BG));
214 init_pair(CP_TAB_BORDER, opt_color_to_color(STR_COLOR_TAB_BORDER_FG),
215 opt_color_to_color(STR_COLOR_TAB_BORDER_BG));
216 init_pair(CP_TAB_LABEL, opt_color_to_color(STR_COLOR_TAB_LABEL_FG),
217 opt_color_to_color(STR_COLOR_TAB_LABEL_BG));
218 init_pair(CP_FIELD_NAME, opt_color_to_color(STR_COLOR_FIELD_NAME_FG),
219 opt_color_to_color(STR_COLOR_FIELD_NAME_BG));
220 init_pair(CP_FIELD_VALUE, opt_color_to_color(STR_COLOR_FIELD_VALUE_FG),
221 opt_color_to_color(STR_COLOR_FIELD_VALUE_BG));
229 fprintf(stderr, "init_abook():\n");
230 fprintf(stderr, " COLS = %d, LINES = %d\n", COLS, LINES);
232 if( LINES < MIN_LINES || COLS < MIN_COLS ) {
233 clear(); refresh(); endwin();
234 fprintf(stderr, _("Your terminal size is %dx%d\n"), COLS, LINES);
235 fprintf(stderr, _("Terminal is too small. Minimum terminal "
237 "%dx%d\n"), MIN_COLS, MIN_LINES);
244 ui_initialized = TRUE;
247 signal(SIGWINCH, win_changed);
262 ui_initialized = FALSE;
267 headerline(const char *str)
271 wattrset(top, COLOR_PAIR(CP_HEADER));
272 mvwhline(top, 0, 0, ' ', COLS);
273 mvwhline(top, 1, 0, UI_HLINE_CHAR, COLS);
275 mvwprintw(top, 0, 0, "%s | %s", PACKAGE " " VERSION, str);
293 refresh_statusline();
294 headerline(gettext(MAIN_HELPLINE));
302 statusline_msg(const char *msg)
307 statusline_addstr(msg);
310 fprintf(stderr, "statusline_msg(\"%s\")\n", msg);
318 statusline_addstr(const char *str)
320 mvwaddstr(bottom, 1, 0, str);
325 /* Same as statusline_addstr(), but hilight "<str>" sequences if the terminal
328 statusline_addhlstr(const char *str)
330 #if defined(A_BOLD) && defined(A_NORMAL) && defined(A_DIM)
331 const char *p = str, *start = str;
336 if(!*p || strchr("<>", *p)) {
338 wattrset(bottom, (*p == '>') ? A_BOLD : A_NORMAL);
339 tmp = xstrndup(start, p - start);
340 mvwaddstr(bottom, 1, pos, tmp);
341 pos += strwidth(tmp);
347 /* show tag markers */
348 wattrset(bottom, A_DIM);
349 mvwaddch(bottom, 1, pos++, *p);
354 wattrset(bottom, A_NORMAL);
361 mvwaddstr(bottom, 1, 0, str);
369 statusline_askchoice(const char *msg, const char *choices, short dflt)
374 assert((dflt >= 0) && (dflt <= strlen(choices)));
377 s = strdup_printf("%s [%c]", msg, choices[dflt - 1]);
378 statusline_addhlstr(s);
381 statusline_addhlstr(msg);
385 ch = tolower(getch());
387 if(ch == 7) /* ctrl+G */
390 if(dflt && (ch == '\r')) /* default choice */
393 if((s = strchr(choices, ch)))
394 return (s - choices + 1);
399 ui_readline(const char *prompt, char *s, size_t limit, bool use_completion)
404 mvwaddstr(bottom, 1, 0, prompt);
408 ret = abook_readline(bottom, y, x, s, use_completion);
412 if(strlen(ret) > limit && limit > 0)
420 statusline_ask_boolean(const char *msg, int def)
423 char *msg2 = strconcat(msg, def ? _(" (Y/n)?") : _(" (y/N)?"), NULL);
426 statusline_addstr(msg2);
430 ch = tolower(getch());
432 if(ch == *(S_("keybinding for no|n")))
434 else if(ch == *(S_("keybinding for yes|y")))
449 wattrset(bottom, COLOR_PAIR(CP_FOOTER));
450 mvwhline(bottom, 0, 0, UI_HLINE_CHAR, COLS);
457 ask_filename(const char *prompt)
463 buf = ui_readline(prompt, NULL, -1, 1);
484 display_help(int help)
500 helpw = newwin(LINES - 5, COLS - 6, 2, 3);
502 headerline(_("help"));
504 for(i = 0; tbl[i] != NULL; i++) {
505 waddstr(helpw, gettext(tbl[i]));
506 if( (!((i + 1) % (LINES - 8))) ||
507 (tbl[i + 1] == NULL) ) {
510 refresh_statusline();
511 if(statusline_msg(_("Press any key to continue..."))
526 extern char *selected;
534 can_resize = TRUE; /* it's safe to resize now */
535 if(!opt_get_bool(BOOL_SHOW_CURSOR))
540 if(!opt_get_bool(BOOL_SHOW_CURSOR))
542 can_resize = FALSE; /* it's not safe to resize anymore */
543 if(ch == KEY_MOUSE) {
545 bool double_clicked = was_double_click();
546 if(getmouse(&event) == OK) {
547 if(event.bstate & BUTTON1_CLICKED
548 || event.bstate & BUTTON1_DOUBLE_CLICKED) {
552 list_set_curitem(event.y + list_get_firstitem() - LIST_TOP);
558 } else if(event.bstate & BUTTON4_PRESSED) {
560 } else if(event.bstate & BUTTON5_PRESSED) {
567 case 'Q': quit_abook(QUIT_DONTSAVE); break;
568 case 'P': print_stderr(selected_items() ?
569 -1 : list_get_curitem());
572 display_help(HELP_MAIN);
575 case 'a': add_item(); break;
576 case '\r': edit_item(-1); break;
579 case 'r': ui_remove_items(); break;
580 case 'M': ui_merge_items(); break;
581 case 'D': duplicate_item(); break;
582 case 'U': ui_remove_duplicates(); break;
583 case 12: refresh_screen(); break;
586 case KEY_UP: scroll_up(); break;
588 case KEY_DOWN: scroll_down(); break;
590 case KEY_PPAGE: page_up(); break;
592 case KEY_NPAGE: page_down(); break;
595 case KEY_HOME: goto_home(); break;
597 case KEY_END: goto_end(); break;
599 case 'w': save_database();
601 case 'l': ui_read_database(); break;
602 case 'i': import_database(); break;
603 case 'e': export_database(); break;
604 case 'C': ui_clear_database(); break;
606 case 'o': ui_open_datafile(); break;
608 case 's': sort_by_field("name");break;
609 case 'S': sort_surname(); break;
610 case 'F': sort_by_field(NULL); break;
612 case '/': ui_find(0); break;
614 case '\\': ui_find(1); break;
616 case ' ': if(list_get_curitem() >= 0) {
617 list_invert_curitem_selection();
618 ui_print_number_of_items();
622 case '+': select_all();
625 case '-': select_none();
628 case '*': invert_selection();
631 case 'A': move_curitem(MOVE_ITEM_UP);
633 case 'Z': move_curitem(MOVE_ITEM_DOWN);
636 case 'm': launch_mutt(selected_items() ?
637 -1 : list_get_curitem());
641 case 'p': ui_print_database(); break;
643 case 'v': launch_wwwbrowser(list_get_curitem());
656 if(statusline_ask_boolean(_("Remove selected item(s)"), TRUE))
657 remove_selected_items();
666 if(statusline_ask_boolean(_("Merge selected items"), TRUE))
667 merge_selected_items();
673 void ui_remove_duplicates()
675 if(statusline_ask_boolean(_("Remove duplicates"), TRUE))
685 if(statusline_ask_boolean(_("Clear WHOLE database"), FALSE)) {
695 static char findstr[MAX_FIELD_LEN];
696 int search_fields[] = {NAME, EMAIL, NICK, -1};
705 s = ui_readline("/", findstr, MAX_FIELD_LEN - 1, 0);
708 return; /* user cancelled (ctrl-G) */
710 strncpy(findstr, s, MAX_FIELD_LEN);
715 if( (item = find_item(findstr, list_get_curitem() + !!next,
716 search_fields)) < 0 &&
717 (item = find_item(findstr, 0, search_fields)) >= 0)
718 statusline_addstr(_("Search hit bottom, continuing at top"));
721 list_set_curitem(item);
727 ui_print_number_of_items()
729 char *str = strdup_printf(" " "|%3d/%3d",
730 selected_items(), db_n_items());
732 attrset(COLOR_PAIR(CP_HEADER));
733 mvaddstr(0, COLS-strlen(str), str);
743 if(!list_is_empty()) {
744 msg = strdup_printf(_("Your current data will be lost - "
745 "Press '%c' to continue"),
746 *(S_("keybinding for yes|y")));
747 if(!statusline_ask_boolean(msg, FALSE)) {
754 load_database(datafile);
763 char *command = opt_get_str(STR_PRINT_COMMAND);
769 switch(statusline_askchoice(_("Print <a>ll, print <s>elected, or <c>ancel?"), S_("keybindings:all/selected/cancel|asc"), 3)) {
774 if( !selected_items() ) {
775 statusline_msg(_("No selected items"));
778 mode = ENUM_SELECTED;
787 if( ! *command || (handle = popen(command, "w")) == NULL)
790 fexport("text", handle, mode);
801 filename = ask_filename(_("File to open: "));
803 if(!filename || ! *filename) {
809 if(opt_get_bool(BOOL_AUTOSAVE))
811 else if(statusline_ask_boolean(_("Save current database"), FALSE))
816 load_database(filename);
818 if(list_is_empty()) {
819 statusline_msg(_("Sorry, the specified file appears not to be a valid abook addressbook"));
820 load_database(datafile);
823 datafile = xstrdup(filename);
829 alternative_datafile = TRUE;