/* $calcurse: wins.c,v 1.33 2010/03/23 13:08:46 culot Exp $ */ /* * Calcurse - text-based organizer * * Copyright (c) 2007-2010 Frederic Culot * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Send your feedback or comments to : calcurse@culot.org * Calcurse home page : http://culot.org/calcurse * */ #include #include #include #include "calcurse.h" /* Variables to handle calcurse windows. */ struct window win[NBWINS]; /* User-configurable side bar width. */ static unsigned sbarwidth; static enum win slctd_win; static int layout; /* * The screen_mutex mutex and wins_refresh(), wins_wrefresh(), wins_doupdate() * functions are used to prevent concurrent updates of the screen. * It was observed that the display could get screwed up when mulitple threads * tried to refresh the screen at the same time. * * Note (2010-03-21): * Since recent versions of ncurses (5.7), rudimentary support for threads are * available (use_screen(), use_window() for instance - see curs_threads(3)), * but to remain compatible with earlier versions, it was chosen to rely on a * screen-level mutex instead. */ static pthread_mutex_t screen_mutex = PTHREAD_MUTEX_INITIALIZER; static unsigned screen_acquire (void) { if (pthread_mutex_lock (&screen_mutex) != 0) return 0; else return 1; } static void screen_release (void) { (void)pthread_mutex_unlock (&screen_mutex); } int wins_refresh (void) { int rc; if (!screen_acquire ()) return ERR; rc = refresh (); screen_release (); return rc; } int wins_wrefresh (WINDOW *win) { int rc; if (!win || !screen_acquire ()) return ERR; rc = wrefresh (win); screen_release (); return rc; } int wins_doupdate (void) { int rc; if (!screen_acquire ()) return ERR; rc = doupdate (); screen_release (); return rc; } /* Get the current layout. */ int wins_layout (void) { return layout; } /* Set the current layout. */ void wins_set_layout (int nb) { layout = nb; } /* Get the current side bar width. */ unsigned wins_sbar_width (void) { return sbarwidth ? sbarwidth : SBARMINWIDTH; } /* * Return the side bar width in percentage of the total number of columns * available in calcurse's screen. */ unsigned wins_sbar_wperc (void) { unsigned perc; perc = col ? (unsigned)(100 * sbarwidth / col + 1): 0; return perc > SBARMAXWIDTHPERC ? SBARMAXWIDTHPERC : perc; } /* * Set side bar width (unit: number of characters) given a width in percentage * of calcurse's screen total width. * The side bar could not have a width representing more than 50% of the screen, * and could not be less than SBARMINWIDTH characters. */ void wins_set_sbar_width (unsigned perc) { if (perc > SBARMAXWIDTHPERC) sbarwidth = col * SBARMAXWIDTHPERC / 100; else if (perc <= 0) sbarwidth = SBARMINWIDTH; else { sbarwidth = (unsigned)(col * perc / 100); if (sbarwidth < SBARMINWIDTH) sbarwidth = SBARMINWIDTH; } } /* Change the width of the side bar within acceptable boundaries. */ void wins_sbar_winc (void) { if (sbarwidth < SBARMINWIDTH) sbarwidth = SBARMINWIDTH + 1; else if (sbarwidth < SBARMAXWIDTHPERC * col / 100) sbarwidth++; } void wins_sbar_wdec (void) { if (sbarwidth > SBARMINWIDTH) sbarwidth--; } /* Initialize the selected window in calcurse's interface. */ void wins_slctd_init (void) { wins_slctd_set (CAL); } /* Returns an enum which corresponds to the window which is selected. */ enum win wins_slctd (void) { return (slctd_win); } /* Sets the selected window. */ void wins_slctd_set (enum win window) { slctd_win = window; } /* TAB key was hit in the interface, need to select next window. */ void wins_slctd_next (void) { if (slctd_win == TOD) slctd_win = CAL; else slctd_win++; } static void wins_init_panels (void) { char label[BUFSIZ]; win[CAL].p = newwin (CALHEIGHT, wins_sbar_width (), win[CAL].y, win[CAL].x); (void)snprintf (label, BUFSIZ, _("Calendar")); wins_show (win[CAL].p, label); win[APP].p = newwin (win[APP].h, win[APP].w, win[APP].y, win[APP].x); (void)snprintf (label, BUFSIZ, _("Appointments")); wins_show (win[APP].p, label); apad.width = win[APP].w - 3; apad.ptrwin = newpad (apad.length, apad.width); win[TOD].p = newwin (win[TOD].h, win[TOD].w, win[TOD].y, win[TOD].x); (void)snprintf (label, BUFSIZ, _("ToDo")); wins_show (win[TOD].p, label); /* Enable function keys (i.e. arrow keys) in those windows */ keypad (win[CAL].p, TRUE); keypad (win[APP].p, TRUE); keypad (win[TOD].p, TRUE); } /* Create all the windows. */ void wins_init (void) { wins_init_panels (); win[STA].p = newwin (win[STA].h, win[STA].w, win[STA].y, win[STA].x); keypad (win[STA].p, TRUE); /* Notify that the curses mode is now launched. */ ui_mode = UI_CURSES; } /* * Create a new window and its associated pad, which is used to make the * scrolling faster. */ void wins_scrollwin_init (struct scrollwin *sw) { EXIT_IF (sw == 0, "null pointer"); sw->win.p = newwin (sw->win.h, sw->win.w, sw->win.y, sw->win.x); sw->pad.p = newpad (sw->pad.h, sw->pad.w); sw->first_visible_line = 0; sw->total_lines = 0; } /* Free an already created scrollwin. */ void wins_scrollwin_delete (struct scrollwin *sw) { EXIT_IF (sw == 0, "null pointer"); delwin(sw->win.p); delwin(sw->pad.p); } /* Display a scrolling window. */ void wins_scrollwin_display (struct scrollwin *sw) { const int visible_lines = sw->win.h - sw->pad.y - 1; if (sw->total_lines > visible_lines) { float ratio = ((float) visible_lines) / ((float) sw->total_lines); int sbar_length = (int) (ratio * visible_lines); int highend = (int) (ratio * sw->first_visible_line); int sbar_top = highend + sw->pad.y + 1; if ((sbar_top + sbar_length) > sw->win.h - 1) sbar_length = sw->win.h - sbar_top; draw_scrollbar (sw->win.p, sbar_top, sw->win.w + sw->win.x - 2, sbar_length, sw->pad.y + 1, sw->win.h - 1, 1); } wmove (win[STA].p, 0, 0); wnoutrefresh (sw->win.p); pnoutrefresh (sw->pad.p, sw->first_visible_line, 0, sw->pad.y, sw->pad.x, sw->win.h - sw->pad.y + 1, sw->win.w - sw->win.x); wins_doupdate (); } void wins_scrollwin_up (struct scrollwin *sw, int amount) { if (sw->first_visible_line > 0) sw->first_visible_line -= amount; } void wins_scrollwin_down (struct scrollwin *sw, int amount) { if (sw->total_lines > (sw->first_visible_line + sw->win.h - sw->pad.y - 1)) sw->first_visible_line += amount; } void wins_reinit_panels (void) { delwin (win[CAL].p); delwin (win[APP].p); delwin (apad.ptrwin); delwin (win[TOD].p); wins_get_config (); wins_init_panels (); } /* * Delete the existing windows and recreate them with their new * size and placement. */ void wins_reinit (void) { delwin (win[CAL].p); delwin (win[APP].p); delwin (apad.ptrwin); delwin (win[TOD].p); delwin (win[STA].p); wins_get_config (); wins_init (); if (notify_bar ()) notify_reinit_bar (); } /* Show the window with a border and a label. */ void wins_show (WINDOW *win, char *label) { int startx, starty, height, width; getbegyx (win, starty, startx); getmaxyx (win, height, width); box (win, 0, 0); mvwaddch (win, 2, 0, ACS_LTEE); mvwhline (win, 2, 1, ACS_HLINE, width - 2); mvwaddch (win, 2, width - 1, ACS_RTEE); print_in_middle (win, 1, 0, width, label); } /* * Get the screen size and recalculate the windows configurations. */ void wins_get_config (void) { /* Get the screen configuration */ getmaxyx (stdscr, row, col); /* fixed values for status, notification bars and calendar */ win[STA].h = STATUSHEIGHT; win[STA].w = col; win[STA].y = row - win[STA].h; win[STA].x = 0; if (notify_bar ()) { win[NOT].h = 1; win[NOT].w = col; win[NOT].y = win[STA].y - 1; win[NOT].x = 0; } else { win[NOT].h = 0; win[NOT].w = 0; win[NOT].y = 0; win[NOT].x = 0; } win[CAL].w = wins_sbar_width (); win[CAL].h = CALHEIGHT; if (layout <= 4) { /* APPOINTMENT is the biggest panel */ win[APP].w = col - win[CAL].w; win[APP].h = row - (win[STA].h + win[NOT].h); win[TOD].w = win[CAL].w; win[TOD].h = row - (win[CAL].h + win[STA].h + win[NOT].h); } else { /* TODO is the biggest panel */ win[TOD].w = col - win[CAL].w; win[TOD].h = row - (win[STA].h + win[NOT].h); win[APP].w = win[CAL].w; win[APP].h = row - (win[CAL].h + win[STA].h + win[NOT].h); } /* defining the layout */ switch (layout) { case 1: win[APP].y = 0; win[APP].x = 0; win[CAL].y = 0; win[TOD].x = win[APP].w; win[TOD].y = win[CAL].h; win[CAL].x = win[APP].w; break; case 2: win[APP].y = 0; win[APP].x = 0; win[TOD].y = 0; win[TOD].x = win[APP].w; win[CAL].x = win[APP].w; win[CAL].y = win[TOD].h; break; case 3: win[APP].y = 0; win[TOD].x = 0; win[CAL].x = 0; win[CAL].y = 0; win[APP].x = win[CAL].w; win[TOD].y = win[CAL].h; break; case 4: win[APP].y = 0; win[TOD].x = 0; win[TOD].y = 0; win[CAL].x = 0; win[APP].x = win[CAL].w; win[CAL].y = win[TOD].h; break; case 5: win[TOD].y = 0; win[TOD].x = 0; win[CAL].y = 0; win[APP].y = win[CAL].h; win[APP].x = win[TOD].w; win[CAL].x = win[TOD].w; break; case 6: win[TOD].y = 0; win[TOD].x = 0; win[APP].y = 0; win[APP].x = win[TOD].w; win[CAL].x = win[TOD].w; win[CAL].y = win[APP].h; break; case 7: win[TOD].y = 0; win[APP].x = 0; win[CAL].x = 0; win[CAL].y = 0; win[TOD].x = win[CAL].w; win[APP].y = win[CAL].h; break; case 8: win[TOD].y = 0; win[APP].x = 0; win[CAL].x = 0; win[APP].y = 0; win[TOD].x = win[CAL].w; win[CAL].y = win[APP].h; break; } } /* draw panel border in color */ static void border_color (WINDOW *window) { int color_attr = A_BOLD; int no_color_attr = A_BOLD; if (colorize) { wattron (window, color_attr | COLOR_PAIR (COLR_CUSTOM)); box (window, 0, 0); } else { wattron (window, no_color_attr); box (window, 0, 0); } if (colorize) { wattroff (window, color_attr | COLOR_PAIR (COLR_CUSTOM)); } else { wattroff (window, no_color_attr); } wnoutrefresh (window); } /* draw panel border without any color */ static void border_nocolor (WINDOW *window) { int color_attr = A_BOLD; int no_color_attr = A_DIM; if (colorize) { wattron (window, color_attr | COLOR_PAIR (COLR_DEFAULT)); } else { wattron (window, no_color_attr); } box (window, 0, 0); if (colorize) { wattroff (window, color_attr | COLOR_PAIR (COLR_DEFAULT)); } else { wattroff (window, no_color_attr); } wnoutrefresh (window); } void wins_update_border (void) { switch (slctd_win) { case CAL: border_color (win[CAL].p); border_nocolor (win[APP].p); border_nocolor (win[TOD].p); break; case APP: border_color (win[APP].p); border_nocolor (win[CAL].p); border_nocolor (win[TOD].p); break; case TOD: border_color (win[TOD].p); border_nocolor (win[APP].p); border_nocolor (win[CAL].p); break; default: EXIT (_("no window selected")); /* NOTREACHED */ } } void wins_update_panels (void) { apoint_update_panel (slctd_win); todo_update_panel (slctd_win); calendar_update_panel (&win[CAL]); } /* * Update all of the three windows and put a border around the * selected window. */ void wins_update (void) { wins_update_border (); wins_update_panels (); wins_status_bar (); if (notify_bar ()) notify_update_bar (); wmove (win[STA].p, 0, 0); wins_doupdate (); } /* Reset the screen, needed when resizing terminal for example. */ void wins_reset (void) { endwin (); wins_refresh (); curs_set (0); wins_reinit (); wins_update (); } /* * While inside interactive mode, launch the external command cmd on the given * file. */ void wins_launch_external (const char *file, const char *cmd) { char *p; int len; /* Beware of space between cmd and file. */ len = strlen (file) + strlen (cmd) + 2; p = (char *) mem_calloc (len, sizeof (char)); if (snprintf (p, len, "%s %s", cmd, file) == -1) { mem_free (p); return; } if (notify_bar ()) notify_stop_main_thread (); def_prog_mode (); endwin (); ui_mode = UI_CMDLINE; clear (); wins_refresh (); (void)system (p); reset_prog_mode (); clearok (curscr, TRUE); curs_set (0); ui_mode = UI_CURSES; wins_refresh (); if (notify_bar ()) notify_start_main_thread (); mem_free (p); } #define NB_CAL_CMDS 27 /* number of commands while in cal view */ #define NB_APP_CMDS 31 /* same thing while in appointment view */ #define NB_TOD_CMDS 30 /* same thing while in todo view */ #define TOTAL_CMDS NB_CAL_CMDS + NB_APP_CMDS + NB_TOD_CMDS #define CMDS_PER_LINE 6 /* max number of commands per line */ static unsigned status_page; /* * Draws the status bar. * To add a keybinding, insert a new binding_t item, add it in the *binding * table, and update the NB_CAL_CMDS, NB_APP_CMDS or NB_TOD_CMDS defines, * depending on which panel the added keybind is assigned to. */ void wins_status_bar (void) { #define NB_PANELS 3 /* 3 panels: CALENDAR, APPOINTMENT, TODO */ enum win which_pan; int start, end; const int pos[NB_PANELS + 1] = { 0, NB_CAL_CMDS, NB_CAL_CMDS + NB_APP_CMDS, TOTAL_CMDS }; struct binding help = {_("Help"), KEY_GENERIC_HELP}; struct binding quit = {_("Quit"), KEY_GENERIC_QUIT}; struct binding save = {_("Save"), KEY_GENERIC_SAVE}; struct binding cut = {_("Cut"), KEY_GENERIC_CUT}; struct binding paste = {_("Paste"), KEY_GENERIC_PASTE}; struct binding chgvu = {_("Chg Win"), KEY_GENERIC_CHANGE_VIEW}; struct binding import = {_("Import"), KEY_GENERIC_IMPORT}; struct binding export = {_("Export"), KEY_GENERIC_EXPORT}; struct binding togo = {_("Go to"), KEY_GENERIC_GOTO}; struct binding othr = {_("OtherCmd"), KEY_GENERIC_OTHER_CMD}; struct binding conf = {_("Config"), KEY_GENERIC_CONFIG_MENU}; struct binding draw = {_("Redraw"), KEY_GENERIC_REDRAW}; struct binding appt = {_("Add Appt"), KEY_GENERIC_ADD_APPT}; struct binding todo = {_("Add Todo"), KEY_GENERIC_ADD_TODO}; struct binding gnday = {_("+1 Day"), KEY_GENERIC_NEXT_DAY}; struct binding gpday = {_("-1 Day"), KEY_GENERIC_PREV_DAY}; struct binding gnweek = {_("+1 Week"), KEY_GENERIC_NEXT_WEEK}; struct binding gpweek = {_("-1 Week"), KEY_GENERIC_PREV_WEEK}; struct binding today = {_("Today"), KEY_GENERIC_GOTO_TODAY}; struct binding nview = {_("Nxt View"), KEY_GENERIC_SCROLL_DOWN}; struct binding pview = {_("Prv View"), KEY_GENERIC_SCROLL_UP}; struct binding up = {_("Up"), KEY_MOVE_UP}; struct binding down = {_("Down"), KEY_MOVE_DOWN}; struct binding left = {_("Left"), KEY_MOVE_LEFT}; struct binding right = {_("Right"), KEY_MOVE_RIGHT}; struct binding weekb = {_("beg Week"), KEY_START_OF_WEEK}; struct binding weeke = {_("end Week"), KEY_END_OF_WEEK}; struct binding add = {_("Add Item"), KEY_ADD_ITEM}; struct binding del = {_("Del Item"), KEY_DEL_ITEM}; struct binding edit = {_("Edit Itm"), KEY_EDIT_ITEM}; struct binding view = {_("View"), KEY_VIEW_ITEM}; struct binding flag = {_("Flag Itm"), KEY_FLAG_ITEM}; struct binding rept = {_("Repeat"), KEY_REPEAT_ITEM}; struct binding enote = {_("EditNote"), KEY_EDIT_NOTE}; struct binding vnote = {_("ViewNote"), KEY_VIEW_NOTE}; struct binding rprio = {_("Prio.+"), KEY_RAISE_PRIORITY}; struct binding lprio = {_("Prio.-"), KEY_LOWER_PRIORITY}; struct binding *binding[TOTAL_CMDS] = { /* calendar keys */ &help, &quit, &save, &chgvu, &nview, &pview, &up, &down, &left, &right, &togo, &othr, &import, &export, &weekb, &weeke, &appt, &todo, &gnday, &gpday, &gnweek, &gpweek, &draw, &othr, &today, &conf, &othr, /* appointment keys */ &help, &quit, &save, &chgvu, &import, &export, &add, &del, &edit, &view, &draw, &othr, &rept, &flag, &enote, &vnote, &up, &down, &gnday, &gpday, &gnweek, &gpweek, &togo, &othr, &today, &conf, &appt, &todo, &cut, &paste, &othr, /* todo keys */ &help, &quit, &save, &chgvu, &import, &export, &add, &del, &edit, &view, &flag, &othr, &rprio, &lprio, &enote, &vnote, &up, &down, &gnday, &gpday, &gnweek, &gpweek, &togo, &othr, &today, &conf, &appt, &todo, &draw, &othr }; /* Drawing the keybinding with attribute and label without. */ which_pan = wins_slctd (); start = pos[which_pan] + 2 * KEYS_CMDS_PER_LINE * (status_page - 1); end = MIN (start + 2 * KEYS_CMDS_PER_LINE, pos[which_pan + 1]); keys_display_bindings_bar (win[STA].p, binding, start, end); } /* Erase status bar. */ void wins_erase_status_bar (void) { erase_window_part (win[STA].p, 0, 0, col, STATUSHEIGHT); } /* Update the status bar page number to display other commands. */ void wins_other_status_page (int panel) { int nb_item, max_page; nb_item = 0; switch (panel) { case CAL: nb_item = NB_CAL_CMDS; break; case APP: nb_item = NB_APP_CMDS; break; case TOD: nb_item = NB_TOD_CMDS; break; default: EXIT (_("unknown panel")); /* NOTREACHED */ } max_page = ceil (nb_item / (2 * CMDS_PER_LINE + 1)) + 1; if (status_page < max_page) status_page++; else status_page = 1; } /* Reset the status bar page. */ void wins_reset_status_page (void) { status_page = 1; } #undef NB_CAL_CMDS #undef NB_APP_CMDS #undef NB_TOD_CMDS #undef TOTAL_CMDS #undef CMDS_PER_LINE