X-Git-Url: https://git.deb.at/w?p=pkg%2Fabook.git;a=blobdiff_plain;f=ui.c;h=e8798858270654eeccb9daa5a6dbee2e0b1479e9;hp=3286d21b4402a7b52a1b0482ee5c7b44b337bd78;hb=b722eae92e8deb6bd080dea1f2f929a7dd9edddb;hpb=3212da92838c056bcc1cafa57beaf26086e2ac96 diff --git a/ui.c b/ui.c index 3286d21..e879885 100644 --- a/ui.c +++ b/ui.c @@ -2,7 +2,7 @@ /* * $Id$ * - * by JH + * by JH * * Copyright (C) Jaakko Heinonen */ @@ -13,61 +13,55 @@ #include #include #include +#include #include "abook.h" +#include #include "ui.h" #include "edit.h" #include "database.h" +#include "gettext.h" #include "list.h" #include "misc.h" #include "options.h" #include "filter.h" -#include "estr.h" +#include "xmalloc.h" +#include "color.h" +#include #ifdef HAVE_CONFIG_H # include "config.h" #endif -#ifdef HAVE_TERMIOS_H -# include -#else -# ifdef HAVE_LINUX_TERMIOS_H -# include -# endif -#endif #ifdef HAVE_SYS_IOCTL_H # include #endif -#ifdef USE_ASCII_ONLY -# define UI_HLINE_CHAR '-' -#else -# define UI_HLINE_CHAR ACS_HLINE -#endif - +#include "abook_rl.h" /* * external variables */ -extern int items, curitem; extern char *datafile; +extern bool alternative_datafile; + /* * internal variables */ -int ui_initialized = FALSE; +static bool ui_initialized = FALSE; -int should_resize = FALSE; -int can_resize = FALSE; - -WINDOW *top = NULL, *bottom = NULL; +static bool should_resize = FALSE; +static bool can_resize = FALSE; +static struct timeval last_click_time; +static int double_click_interval = 200; /* maximum time in milliseconds */ +static WINDOW *top = NULL, *bottom = NULL; static void init_windows() { top = newwin(LIST_TOP - 1, COLS, 0, 0); - bottom = newwin(LINES - LIST_BOTTOM, COLS, LIST_BOTTOM, 0); } @@ -86,13 +80,13 @@ resize_abook() #ifdef TIOCGWINSZ struct winsize winsz; - ioctl (0, TIOCGWINSZ, &winsz); + ioctl(0, TIOCGWINSZ, &winsz); #ifdef DEBUG if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) { fprintf(stderr, "Warning: COLS=%d, LINES=%d\n", winsz.ws_col, winsz.ws_row); } #endif - + if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) { #ifdef HAVE_RESIZETERM resizeterm(winsz.ws_row, winsz.ws_col); @@ -116,10 +110,10 @@ resize_abook() static void win_changed(int i) { - if( can_resize ) + if(can_resize) resize_abook(); else - should_resize = TRUE; + should_resize = TRUE; } #endif /* SIGWINCH */ @@ -130,36 +124,129 @@ is_ui_initialized() return ui_initialized; } - -int -init_ui() +void +ui_init_curses() { - initscr(); cbreak(); noecho(); + if(!is_ui_initialized()) + initscr(); + cbreak(); + noecho(); nonl(); intrflush(stdscr, FALSE); + if(opt_get_bool(BOOL_USE_MOUSE)) { + mouseinterval(0); + timerclear(&last_click_time); + ui_enable_mouse(TRUE); + } keypad(stdscr, TRUE); + if(opt_get_bool(BOOL_USE_COLORS) && has_colors()) { + start_color(); + use_default_colors(); + ui_init_color_pairs_user(); + } +} + +void +ui_enable_mouse(bool enabled) +{ + mmask_t mask; + if(enabled) { + mask = BUTTON1_CLICKED | BUTTON4_PRESSED; +#if NCURSES_MOUSE_VERSION == 2 + mask |= BUTTON5_PRESSED; +#endif + } else { + mask = 0; + } + mousemask(mask, NULL); +} + +/** Check the time elapsed since last click and tell if it should be + * interpreted as a double click + */ +static bool +was_double_click() { + struct timeval click_time, click_diff, maxdiff; + maxdiff.tv_sec = double_click_interval / 1000; + maxdiff.tv_usec = (double_click_interval % 1000)*1000; + gettimeofday(&click_time, NULL); + + timersub(&click_time, &last_click_time, &click_diff); + last_click_time = click_time; + return !timercmp(&click_diff, &maxdiff, >); +} + +#define CHECK_COLOR_NAME(value, name, DEFNAME) \ + if(!strcmp((name), (value))){ \ + return DEFNAME; \ + } +short +opt_color_to_color(enum str_opts enum_name) +{ + char* name = opt_get_str(enum_name); + CHECK_COLOR_NAME(name, "default", COLOR_DEFAULT) + else CHECK_COLOR_NAME(name, "black", COLOR_BLACK) + else CHECK_COLOR_NAME(name, "red", COLOR_RED) + else CHECK_COLOR_NAME(name, "green", COLOR_GREEN) + else CHECK_COLOR_NAME(name, "yellow", COLOR_YELLOW) + else CHECK_COLOR_NAME(name, "blue", COLOR_BLUE) + else CHECK_COLOR_NAME(name, "magenta", COLOR_MAGENTA) + else CHECK_COLOR_NAME(name, "cyan", COLOR_CYAN) + else CHECK_COLOR_NAME(name, "white", COLOR_WHITE) + else return COLOR_DEFAULT; +} + +void +ui_init_color_pairs_user() +{ + init_pair(CP_HEADER, opt_color_to_color(STR_COLOR_HEADER_FG), + opt_color_to_color(STR_COLOR_HEADER_BG)); + init_pair(CP_FOOTER, opt_color_to_color(STR_COLOR_FOOTER_FG), + opt_color_to_color(STR_COLOR_FOOTER_BG)); + init_pair(CP_LIST_EVEN, opt_color_to_color(STR_COLOR_LIST_EVEN_FG), + opt_color_to_color(STR_COLOR_LIST_EVEN_BG)); + init_pair(CP_LIST_ODD, opt_color_to_color(STR_COLOR_LIST_ODD_FG), + opt_color_to_color(STR_COLOR_LIST_ODD_BG)); + init_pair(CP_LIST_HEADER, opt_color_to_color(STR_COLOR_LIST_HEADER_FG), + opt_color_to_color(STR_COLOR_LIST_HEADER_BG)); + init_pair(CP_LIST_HIGHLIGHT, opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_FG), + opt_color_to_color(STR_COLOR_LIST_HIGHLIGHT_BG)); + init_pair(CP_TAB_BORDER, opt_color_to_color(STR_COLOR_TAB_BORDER_FG), + opt_color_to_color(STR_COLOR_TAB_BORDER_BG)); + init_pair(CP_TAB_LABEL, opt_color_to_color(STR_COLOR_TAB_LABEL_FG), + opt_color_to_color(STR_COLOR_TAB_LABEL_BG)); + init_pair(CP_FIELD_NAME, opt_color_to_color(STR_COLOR_FIELD_NAME_FG), + opt_color_to_color(STR_COLOR_FIELD_NAME_BG)); + init_pair(CP_FIELD_VALUE, opt_color_to_color(STR_COLOR_FIELD_VALUE_FG), + opt_color_to_color(STR_COLOR_FIELD_VALUE_BG)); +} + +int +init_ui() +{ + ui_init_curses(); #ifdef DEBUG fprintf(stderr, "init_abook():\n"); fprintf(stderr, " COLS = %d, LINES = %d\n", COLS, LINES); #endif if( LINES < MIN_LINES || COLS < MIN_COLS ) { clear(); refresh(); endwin(); - fprintf(stderr, "Your terminal size is %dx%d\n", COLS, LINES); - fprintf(stderr, "Terminal is too small. Minium terminal size " - "for abook is " - "%dx%d\n", MIN_COLS, MIN_LINES); + fprintf(stderr, _("Your terminal size is %dx%d\n"), COLS, LINES); + fprintf(stderr, _("Terminal is too small. Minimum terminal " + "size for abook is " + "%dx%d\n"), MIN_COLS, MIN_LINES); return 1; } -#ifdef SIGWINCH - signal(SIGWINCH, win_changed); -#endif - init_list(); init_windows(); ui_initialized = TRUE; +#ifdef SIGWINCH + signal(SIGWINCH, win_changed); +#endif + return 0; } @@ -177,105 +264,181 @@ close_ui() void -headerline(char *str) +headerline(const char *str) { werase(top); - + + wattrset(top, COLOR_PAIR(CP_HEADER)); + mvwhline(top, 0, 0, ' ', COLS); mvwhline(top, 1, 0, UI_HLINE_CHAR, COLS); - + mvwprintw(top, 0, 0, "%s | %s", PACKAGE " " VERSION, str); refresh(); wrefresh(top); } - + void refresh_screen() { #ifdef SIGWINCH - if( should_resize ) { + if(should_resize) { resize_abook(); return; } #endif clear(); - + refresh_statusline(); - headerline(MAIN_HELPLINE); + headerline(gettext(MAIN_HELPLINE)); list_headerline(); refresh_list(); } -void -statusline_msg(char *msg) +int +statusline_msg(const char *msg) { + int c; + clear_statusline(); statusline_addstr(msg); - getch(); + c = getch(); #ifdef DEBUG fprintf(stderr, "statusline_msg(\"%s\")\n", msg); #endif clear_statusline(); + + return c; } void -statusline_addstr(char *str) +statusline_addstr(const char *str) { mvwaddstr(bottom, 1, 0, str); refresh(); wrefresh(bottom); } -/* - * function statusline_getnstr - * - * parameters: - * (char *str) - * if n >= 0 str is a pointer which points a place where to store - * the string, else str is ingnored - * (int n) - * the maximum length of the string - * If n < 0 function will allocate needed space for the string. - * Value 0 is not allowed for n. - * (int use_filesel) - * if this value is nonzero the fileselector is enabled - * - * returns (char *) - * If n < 0 a pointer to a newly allocated string is returned. - * If n > 0 a nonzero value is returned if user has typed a valid - * string. If not NULL value is returned. Never really use the - * _pointer_ if n > 0. - * - */ +/* Same as statusline_addstr(), but hilight "" sequences if the terminal + * supports it */ +static void +statusline_addhlstr(const char *str) +{ +#if defined(A_BOLD) && defined(A_NORMAL) && defined(A_DIM) + const char *p = str, *start = str; + char *tmp; + int pos = 0; + + while(1) { + if(!*p || strchr("<>", *p)) { + if(p - start > 0) { + wattrset(bottom, (*p == '>') ? A_BOLD : A_NORMAL); + tmp = xstrndup(start, p - start); + mvwaddstr(bottom, 1, pos, tmp); + pos += strwidth(tmp); + free(tmp); + } + if(*p) { + start = p + 1; + + /* show tag markers */ + wattrset(bottom, A_DIM); + mvwaddch(bottom, 1, pos++, *p); + } + } + + if(!*p) { + wattrset(bottom, A_NORMAL); + break; + } + + p++; + } +#else + mvwaddstr(bottom, 1, 0, str); +#endif + + refresh(); + wrefresh(bottom); +} + +int +statusline_askchoice(const char *msg, const char *choices, short dflt) +{ + char *s; + int ch; + + assert((dflt >= 0) && (dflt <= strlen(choices))); + + if(dflt) { + s = strdup_printf("%s [%c]", msg, choices[dflt - 1]); + statusline_addhlstr(s); + free(s); + } else + statusline_addhlstr(msg); + + while(1) + { + ch = tolower(getch()); + + if(ch == 7) /* ctrl+G */ + return 0; + + if(dflt && (ch == '\r')) /* default choice */ + return dflt; + + if((s = strchr(choices, ch))) + return (s - choices + 1); + } +} char * -statusline_getnstr(char *str, int n, int use_filesel) +ui_readline(const char *prompt, char *s, size_t limit, bool use_completion) { - char *buf; int y, x; + char *ret; + + mvwaddstr(bottom, 1, 0, prompt); getyx(bottom, y, x); - wmove(bottom, 1, x); - - buf = wenter_string(bottom, n, - (use_filesel ? ESTR_USE_FILESEL:0) | ESTR_DONT_WRAP); - if(n < 0) - return buf; - - if(buf == NULL) - str[0] = 0; - else - strncpy(str, buf, n); + ret = abook_readline(bottom, y, x, s, use_completion); - str[n-1] = 0; + if(ret) { + strtrim(ret); + if(strlen(ret) > limit && limit > 0) + ret[limit] = '\0'; + } - free(buf); + return ret; +} - return buf; +int +statusline_ask_boolean(const char *msg, int def) +{ + int ret; + char *msg2 = strconcat(msg, def ? _(" (Y/n)?") : _(" (y/N)?"), NULL); + char ch; + + statusline_addstr(msg2); + + free(msg2); + + ch = tolower(getch()); + + if(ch == *(S_("keybinding for no|n"))) + ret = FALSE; + else if(ch == *(S_("keybinding for yes|y"))) + ret = TRUE; + else + ret = def; + + clear_statusline(); + + return ret; } void @@ -283,25 +446,21 @@ refresh_statusline() { werase(bottom); + wattrset(bottom, COLOR_PAIR(CP_FOOTER)); mvwhline(bottom, 0, 0, UI_HLINE_CHAR, COLS); - mvwhline(bottom, 2, 0, UI_HLINE_CHAR, COLS); refresh(); wrefresh(bottom); } - char * -ask_filename(char *prompt, int flags) +ask_filename(const char *prompt) { char *buf = NULL; clear_statusline(); - - statusline_addstr(prompt); - buf = statusline_getnstr(NULL, -1, flags); - clear_statusline(); + buf = ui_readline(prompt, NULL, -1, 1); return buf; } @@ -315,12 +474,10 @@ clear_statusline() refresh(); } - /* - * help - need to rewrite + * help */ - #include "help.h" void @@ -342,16 +499,18 @@ display_help(int help) helpw = newwin(LINES - 5, COLS - 6, 2, 3); erase(); - headerline("help"); - - for( i = 0; tbl[i] != NULL; i++) { - waddstr(helpw, tbl[i]); - if( ( !( (i+1) % (LINES-8) ) ) || - (tbl[i+1] == NULL) ) { + headerline(_("help")); + + for(i = 0; tbl[i] != NULL; i++) { + waddstr(helpw, gettext(tbl[i])); + if( (!((i + 1) % (LINES - 8))) || + (tbl[i + 1] == NULL) ) { refresh(); wrefresh(helpw); refresh_statusline(); - statusline_msg("Press any key to continue..."); + if(statusline_msg(_("Press any key to continue...")) + == 'q') + break; wclear(helpw); } } @@ -360,13 +519,11 @@ display_help(int help) delwin(helpw); } - /* * end of help */ -char *selected; -extern int curitem; +extern char *selected; void get_commands() @@ -375,14 +532,42 @@ get_commands() for(;;) { can_resize = TRUE; /* it's safe to resize now */ - hide_cursor(); - if( should_resize ) + if(!opt_get_bool(BOOL_SHOW_CURSOR)) + hide_cursor(); + if(should_resize) refresh_screen(); ch = getch(); - show_cursor(); + if(!opt_get_bool(BOOL_SHOW_CURSOR)) + show_cursor(); can_resize = FALSE; /* it's not safe to resize anymore */ - switch( ch ) { + if(ch == KEY_MOUSE) { + MEVENT event; + bool double_clicked = was_double_click(); + if(getmouse(&event) == OK) { + if(event.bstate & BUTTON1_CLICKED + || event.bstate & BUTTON1_DOUBLE_CLICKED) { + if(event.y == 0) { + return; + } + list_set_curitem(event.y + list_get_firstitem() - LIST_TOP); + if(double_clicked) { + edit_item(-1); + } else { + refresh_list(); + } + } else if(event.bstate & BUTTON4_PRESSED) { + scroll_list_up(); + } else if(event.bstate & BUTTON5_PRESSED) { + scroll_list_down(); + } + } + } + switch(ch) { case 'q': return; + case 'Q': quit_abook(QUIT_DONTSAVE); break; + case 'P': print_stderr(selected_items() ? + -1 : list_get_curitem()); + return; case '?': display_help(HELP_MAIN); refresh_screen(); @@ -392,6 +577,9 @@ get_commands() case KEY_DC: case 'd': case 'r': ui_remove_items(); break; + case 'M': ui_merge_items(); break; + case 'D': duplicate_item(); break; + case 'U': ui_remove_duplicates(); break; case 12: refresh_screen(); break; case 'k': @@ -403,9 +591,9 @@ get_commands() case 'J': case KEY_NPAGE: page_down(); break; - case 'H': + case 'g': case KEY_HOME: goto_home(); break; - case 'E': + case 'G': case KEY_END: goto_end(); break; case 'w': save_database(); @@ -417,14 +605,16 @@ get_commands() case 'o': ui_open_datafile(); break; - case 's': sort_database(); break; + case 's': sort_by_field("name");break; case 'S': sort_surname(); break; + case 'F': sort_by_field(NULL); break; case '/': ui_find(0); break; + case 'n': case '\\': ui_find(1); break; - case ' ': if(curitem >= 0) { - selected[curitem] = !selected[curitem]; + case ' ': if(list_get_curitem() >= 0) { + list_invert_curitem_selection(); ui_print_number_of_items(); refresh_list(); } @@ -443,85 +633,103 @@ get_commands() case 'Z': move_curitem(MOVE_ITEM_DOWN); break; - case 'm': launch_mutt(); break; + case 'm': launch_mutt(selected_items() ? + -1 : list_get_curitem()); + refresh_screen(); + break; case 'p': ui_print_database(); break; - case 'u': launch_lynx(); break; + case 'v': launch_wwwbrowser(list_get_curitem()); + refresh_screen(); + break; } } } - void ui_remove_items() { - if( items < 1 || curitem < 0 ) + if(list_is_empty()) return; - statusline_addstr("Remove selected item(s) (Y/n)"); - switch( getch() ) { - case '\r': - case 'y': - case 'Y': break; - default: - clear_statusline(); - return; - } - - remove_selected_items(); + if(statusline_ask_boolean(_("Remove selected item(s)"), FALSE)) + remove_selected_items(); - clear_statusline(); + clear_statusline(); refresh_list(); } void -ui_clear_database() +ui_merge_items() { + if(statusline_ask_boolean(_("Merge selected items"), FALSE)) + merge_selected_items(); - statusline_addstr("Clear WHOLE database (y/N)"); - switch( getch() ) { - case 'y': - case 'Y': break; - default: - clear_statusline(); - return; - } + clear_statusline(); + refresh_list(); +} - close_database(); +void ui_remove_duplicates() +{ + if(statusline_ask_boolean(_("Remove duplicates"), FALSE)) + remove_duplicates(); + + clear_statusline(); + refresh_list(); +} - refresh_screen(); +void +ui_clear_database() +{ + if(statusline_ask_boolean(_("Clear WHOLE database"), FALSE)) { + close_database(); + refresh_list(); + } } void ui_find(int next) { - int item; - static char findstr[81]; + int item = -1; + static char findstr[MAX_FIELD_LEN]; + int search_fields[] = {NAME, EMAIL, NICK, -1}; + + clear_statusline(); if(next) { - if( !*findstr ) + if(!*findstr) return; } else { - clear_statusline(); - statusline_addstr("/"); - statusline_getnstr(findstr, 67, 0); - clear_statusline(); + char *s; + s = ui_readline("/", findstr, MAX_FIELD_LEN - 1, 0); + refresh_screen(); + if(s == NULL) { + return; /* user cancelled (ctrl-G) */ + } else { + strncpy(findstr, s, MAX_FIELD_LEN); + free(s); + } } - if( (item = find_item(findstr, next ? curitem+1 : curitem)) >= 0 ) { - curitem = item; + if( (item = find_item(findstr, list_get_curitem() + !!next, + search_fields)) < 0 && + (item = find_item(findstr, 0, search_fields)) >= 0) + statusline_addstr(_("Search hit bottom, continuing at top")); + + if(item >= 0) { + list_set_curitem(item); refresh_list(); } - } - void ui_print_number_of_items() { - char *str = mkstr(" " "|%3d/%3d", selected_items(), items); + char *str = strdup_printf(" " "|%3d/%3d", + selected_items(), db_n_items()); + attrset(COLOR_PAIR(CP_HEADER)); mvaddstr(0, COLS-strlen(str), str); free(str); @@ -530,15 +738,17 @@ ui_print_number_of_items() void ui_read_database() { - if(items > 0) { - statusline_addstr("Your current data will be lost - Press 'y' to continue"); - switch( getch() ) { - case 'y': - case 'Y': break; - default: clear_statusline(); - return; + char *msg; + + if(!list_is_empty()) { + msg = strdup_printf(_("Your current data will be lost - " + "Press '%c' to continue"), + *(S_("keybinding for yes|y"))); + if(!statusline_ask_boolean(msg, FALSE)) { + free(msg); + return; } - clear_statusline(); + free(msg); } load_database(datafile); @@ -550,22 +760,35 @@ void ui_print_database() { FILE *handle; - char *command = options_get_str("print_command"); + char *command = opt_get_str(STR_PRINT_COMMAND); + int mode; - statusline_addstr("Print addressbook? (y/N)"); - switch( getch() ) { - case 'y': - case 'Y': + if(list_is_empty()) + return; + + switch(statusline_askchoice(_("Print ll, print elected, or ancel?"), S_("keybindings:all/selected/cancel|asc"), 3)) { + case 1: + mode = ENUM_ALL; + break; + case 2: + if( !selected_items() ) { + statusline_msg(_("No selected items")); + return; + } + mode = ENUM_SELECTED; break; - default: clear_statusline(); return; + default: + refresh_screen(); + return; } + clear_statusline(); if( ! *command || (handle = popen(command, "w")) == NULL) return; - fexport("text", handle); - + fexport("text", handle, mode); + pclose(handle); } @@ -575,37 +798,33 @@ ui_open_datafile() { char *filename; - filename = ask_filename("File to open: ", 1); + filename = ask_filename(_("File to open: ")); - if( !filename ) { + if(!filename || ! *filename) { + free(filename); refresh_screen(); return; } - if( options_get_int("autosave") ) + if(opt_get_bool(BOOL_AUTOSAVE)) + save_database(); + else if(statusline_ask_boolean(_("Save current database"), FALSE)) save_database(); - else { - statusline_addstr("Save current database (y/N)"); - switch( getch() ) { - case 'y': - case 'Y': - save_database(); - default: break; - } - } close_database(); load_database(filename); - if( items == 0 ) { - statusline_msg("Sorry, that specified file appears not to be a valid abook addressbook"); + if(list_is_empty()) { + statusline_msg(_("Sorry, the specified file appears not to be a valid abook addressbook")); load_database(datafile); } else { free(datafile); - datafile = strdup(filename); + datafile = xstrdup(filename); } refresh_screen(); free(filename); + + alternative_datafile = TRUE; }