/*
* Calcurse - text-based organizer
*
* Copyright (c) 2004-2022 calcurse Development Team <misc@calcurse.org>
* 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 : misc@calcurse.org
* Calcurse home page : http://calcurse.org
*
*/
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <langinfo.h>
#include "calcurse.h"
struct attribute {
int color[7];
int nocolor[7];
};
static struct attribute attr;
/*
* Define window attributes (for both color and non-color terminals):
* ATTR_HIGHEST are for window titles
* ATTR_HIGH are for month and days names
* ATTR_MIDDLE are for the selected day inside calendar and appointments panel
* ATTR_LOW are for days inside calendar panel which contains an event
* ATTR_LOWEST are for current day inside calendar panel
*/
void custom_init_attr(void)
{
short col_fg, dummy;
pair_content(COLR_CUSTOM, &col_fg, &dummy);
attr.color[ATTR_HIGHEST] =
(col_fg == -1 || col_fg == 255) ?
COLOR_PAIR(COLR_CUSTOM) | A_BOLD :
COLOR_PAIR(COLR_CUSTOM);
attr.color[ATTR_HIGH] = COLOR_PAIR(COLR_HIGH);
attr.color[ATTR_MIDDLE] = COLOR_PAIR(COLR_RED) | A_BOLD;
attr.color[ATTR_LOW] = COLOR_PAIR(COLR_CYAN);
attr.color[ATTR_LOWEST] = COLOR_PAIR(COLR_YELLOW);
attr.color[ATTR_TRUE] = COLOR_PAIR(COLR_GREEN);
attr.color[ATTR_FALSE] = COLOR_PAIR(COLR_RED);
attr.nocolor[ATTR_HIGHEST] = A_BOLD;
attr.nocolor[ATTR_HIGH] = A_REVERSE;
attr.nocolor[ATTR_MIDDLE] = A_REVERSE;
attr.nocolor[ATTR_LOW] = A_UNDERLINE;
attr.nocolor[ATTR_LOWEST] = A_BOLD;
attr.nocolor[ATTR_TRUE] = A_BOLD;
attr.nocolor[ATTR_FALSE] = A_DIM;
}
/* Apply window attribute */
void custom_apply_attr(WINDOW * win, int attr_num)
{
if (colorize)
wattron(win, attr.color[attr_num]);
else
wattron(win, attr.nocolor[attr_num]);
}
/* Remove window attribute */
void custom_remove_attr(WINDOW * win, int attr_num)
{
if (colorize)
wattroff(win, attr.color[attr_num]);
else
wattroff(win, attr.nocolor[attr_num]);
}
static void set_confwin_attr(struct window *cwin)
{
cwin->h = (notify_bar())? row - 3 : row - 2;
cwin->w = col;
cwin->x = cwin->y = 0;
}
/*
* Create a configuration window and initialize status and notification bar
* (useful in case of window resize).
*/
static void confwin_init(struct window *confwin, const char *label)
{
if (confwin->p) {
erase_window_part(confwin->p, confwin->x, confwin->y,
confwin->x + confwin->w,
confwin->y + confwin->h);
delwin(confwin->p);
}
wins_get_config();
set_confwin_attr(confwin);
confwin->p = newwin(confwin->h, col, 0, 0);
box(confwin->p, 0, 0);
wins_show(confwin->p, label);
delwin(win[STA].p);
win[STA].p =
newwin(win[STA].h, win[STA].w, win[STA].y, win[STA].x);
keypad(win[STA].p, TRUE);
if (notify_bar()) {
notify_reinit_bar();
notify_update_bar();
}
}
static void layout_selection_bar(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_GENERIC_SELECT, KEY_MOVE_UP,
KEY_MOVE_DOWN, KEY_MOVE_LEFT, KEY_MOVE_RIGHT, KEY_GENERIC_HELP
};
int bindings_size = ARRAY_SIZE(bindings);
keys_display_bindings_bar(win[STA].p, bindings, bindings_size, 0,
bindings_size);
}
#define NBLAYOUTS 8
#define LAYOUTSPERCOL 2
/* Used to display available layouts in layout configuration menu. */
static void display_layout_config(struct window *lwin, int mark,
int cursor)
{
#define MARK 88
#define LAYOUTH 5
#define LAYOUTW 9
const char *box = "[ ]";
const int BOXSIZ = strlen(box);
const int NBCOLS = NBLAYOUTS / LAYOUTSPERCOL;
const int COLSIZ = LAYOUTW + BOXSIZ + 1;
const int XSPC = (lwin->w - NBCOLS * COLSIZ) / (NBCOLS + 1);
const int XOFST = (lwin->w - NBCOLS * (XSPC + COLSIZ)) / 2;
const int YSPC =
(lwin->h - 8 - LAYOUTSPERCOL * LAYOUTH) / (LAYOUTSPERCOL + 1);
const int YOFST = (lwin->h - LAYOUTSPERCOL * (YSPC + LAYOUTH)) / 2;
enum { YPOS, XPOS, NBPOS };
int pos[NBLAYOUTS][NBPOS];
const char *layouts[LAYOUTH][NBLAYOUTS] = {
{"+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+"},
{"| | c |", "| | t |", "| c | |", "| t | |", "| | c |", "| | a |", "| c | |", "| a | |"},
{"| a +---+", "| a +---+", "+---+ a |", "|---+ a |", "| t +---+", "| t +---+", "+---+ t |", "+---+ t |"},
{"| | t |", "| | c |", "| t | |", "| c | |", "| | a |", "| | c |", "| a | |", "| c | |"},
{"+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+", "+---+---+"}
};
int i;
for (i = 0; i < NBLAYOUTS; i++) {
pos[i][YPOS] =
YOFST + (i % LAYOUTSPERCOL) * (YSPC + LAYOUTH);
pos[i][XPOS] =
XOFST + (i / LAYOUTSPERCOL) * (XSPC + COLSIZ);
}
for (i = 0; i < NBLAYOUTS; i++) {
int j;
mvwaddstr(lwin->p, pos[i][YPOS] + 2, pos[i][XPOS], box);
if (i == mark)
custom_apply_attr(lwin->p, ATTR_HIGHEST);
for (j = 0; j < LAYOUTH; j++) {
mvwaddstr(lwin->p, pos[i][YPOS] + j,
pos[i][XPOS] + BOXSIZ + 1,
layouts[j][i]);
}
if (i == mark)
custom_remove_attr(lwin->p, ATTR_HIGHEST);
}
mvwaddch(lwin->p, pos[mark][YPOS] + 2, pos[mark][XPOS] + 1, MARK);
/* print cursor */
wmove(lwin->p, pos[cursor][YPOS] + 2, pos[cursor][XPOS] + 1);
wchgat(lwin->p, 1, A_REVERSE, (colorize ? COLR_CUSTOM : 0), NULL);
layout_selection_bar();
wnoutrefresh(win[STA].p);
wnoutrefresh(lwin->p);
wins_doupdate();
if (notify_bar())
notify_update_bar();
}
/* Choose the layout */
void custom_layout_config(void)
{
struct window conf_win;
int key, mark, cursor, need_reset;
const char *label = _("layout configuration");
conf_win.p = NULL;
confwin_init(&conf_win, label);
cursor = mark = wins_layout() - 1;
display_layout_config(&conf_win, mark, cursor);
clear();
while ((key = keys_get(win[KEY].p, NULL, NULL)) != KEY_GENERIC_QUIT) {
need_reset = 0;
switch (key) {
case KEY_GENERIC_SELECT:
mark = cursor;
break;
case KEY_MOVE_DOWN:
if (cursor % LAYOUTSPERCOL < LAYOUTSPERCOL - 1)
cursor++;
break;
case KEY_MOVE_UP:
if (cursor % LAYOUTSPERCOL > 0)
cursor--;
break;
case KEY_MOVE_LEFT:
if (cursor >= LAYOUTSPERCOL)
cursor -= LAYOUTSPERCOL;
break;
case KEY_MOVE_RIGHT:
if (cursor < NBLAYOUTS - LAYOUTSPERCOL)
cursor += LAYOUTSPERCOL;
break;
case KEY_GENERIC_CANCEL:
need_reset = 1;
break;
}
if (resize) {
resize = 0;
endwin();
wins_refresh();
curs_set(0);
need_reset = 1;
}
if (need_reset)
confwin_init(&conf_win, label);
display_layout_config(&conf_win, mark, cursor);
}
wins_set_layout(mark + 1);
delwin(conf_win.p);
}
#undef NBLAYOUTS
#undef LAYOUTSPERCOL
/* Sidebar configuration screen. */
void custom_sidebar_config(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_GENERIC_HELP
};
int key, bindings_size = ARRAY_SIZE(bindings);
keys_display_bindings_bar(win[STA].p, bindings, bindings_size, 0,
bindings_size);
wins_doupdate();
while ((key = keys_get(win[KEY].p, NULL, NULL)) != KEY_GENERIC_QUIT) {
switch (key) {
case KEY_MOVE_UP:
wins_sbar_winc();
break;
case KEY_MOVE_DOWN:
wins_sbar_wdec();
break;
case KEY_RESIZE:
break;
default:
continue;
}
if (resize) {
resize = 0;
wins_reset();
} else {
wins_resize_panels();
wins_update_border(FLAG_ALL);
wins_update_panels(FLAG_ALL);
keys_display_bindings_bar(win[STA].p, bindings,
bindings_size, 0,
bindings_size);
wins_doupdate();
}
}
}
static void color_selection_bar(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_GENERIC_SELECT, KEY_GENERIC_CANCEL,
KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_MOVE_LEFT, KEY_GENERIC_SELECT
};
int bindings_size = ARRAY_SIZE(bindings);
keys_display_bindings_bar(win[STA].p, bindings, bindings_size, 0,
bindings_size);
}
/*
* Used to display available colors in color configuration menu.
* This is useful for window resizing.
*/
static void
display_color_config(struct window *cwin, int *mark_fore, int *mark_back,
int cursor, int theme_changed)
{
#define SIZE (2 * (NBUSERCOLORS + 1))
#define DEFAULTCOLOR 255
#define DEFAULTCOLOR_EXT -1
#define MARK 88
const char *fore_txt = _("Foreground");
const char *back_txt = _("Background");
const char *default_txt = _("(terminal's default)");
const char *bar = " ";
const char *box = "[ ]";
const unsigned Y = 3;
const unsigned XOFST = 5;
const unsigned YSPC = (cwin->h - 8) / (NBUSERCOLORS + 1);
const unsigned BARSIZ = strlen(bar);
const unsigned BOXSIZ = strlen(box);
const unsigned XSPC = (cwin->w - 2 * BARSIZ - 2 * BOXSIZ - 6) / 3;
const unsigned XFORE = XSPC;
const unsigned XBACK = 2 * XSPC + BOXSIZ + XOFST + BARSIZ;
enum { YPOS, XPOS, NBPOS };
unsigned i;
int pos[SIZE][NBPOS];
short colr_fore, colr_back, dummy;
int colr[SIZE] = {
COLR_RED, COLR_GREEN, COLR_YELLOW, COLR_BLUE,
COLR_MAGENTA, COLR_CYAN, COLR_DEFAULT,
COLR_RED, COLR_GREEN, COLR_YELLOW, COLR_BLUE,
COLR_MAGENTA, COLR_CYAN, COLR_DEFAULT
};
for (i = 0; i < NBUSERCOLORS + 1; i++) {
pos[i][YPOS] = Y + YSPC * (i + 1);
pos[NBUSERCOLORS + i + 1][YPOS] = Y + YSPC * (i + 1);
pos[i][XPOS] = XFORE;
pos[NBUSERCOLORS + i + 1][XPOS] = XBACK;
}
if (colorize) {
if (theme_changed) {
pair_content(colr[*mark_fore], &colr_fore, &dummy);
if (colr_fore == 255)
colr_fore = -1;
pair_content(colr[*mark_back], &colr_back, &dummy);
if (colr_back == 255)
colr_back = -1;
init_pair(COLR_CUSTOM, colr_fore, colr_back);
custom_init_attr();
} else {
/* Retrieve the actual color theme. */
pair_content(COLR_CUSTOM, &colr_fore, &colr_back);
if ((colr_fore == DEFAULTCOLOR)
|| (colr_fore == DEFAULTCOLOR_EXT)) {
*mark_fore = NBUSERCOLORS;
} else {
for (i = 0; i < NBUSERCOLORS; i++)
/* WARNING. Colour pair number used as colour number. */
if (colr_fore == colr[i])
*mark_fore = i;
}
if ((colr_back == DEFAULTCOLOR)
|| (colr_back == DEFAULTCOLOR_EXT)) {
*mark_back = SIZE - 1;
} else {
for (i = 0; i < NBUSERCOLORS; i++)
/* WARNING. Colour pair number used as colour number. */
if (colr_back ==
colr[NBUSERCOLORS + 1 + i])
*mark_back =
NBUSERCOLORS + 1 + i;
}
}
}
/* color boxes */
for (i = 0; i < SIZE - 1; i++) {
mvwaddstr(cwin->p, pos[i][YPOS], pos[i][XPOS], box);
wattron(cwin->p, COLOR_PAIR(colr[i]) | A_REVERSE);
mvwaddstr(cwin->p, pos[i][YPOS], pos[i][XPOS] + XOFST,
bar);
wattroff(cwin->p, COLOR_PAIR(colr[i]) | A_REVERSE);
}
/* Terminal's default color */
i = SIZE - 1;
mvwaddstr(cwin->p, pos[i][YPOS], pos[i][XPOS], box);
wattron(cwin->p, COLOR_PAIR(colr[i]));
mvwaddstr(cwin->p, pos[i][YPOS], pos[i][XPOS] + XOFST, bar);
wattroff(cwin->p, COLOR_PAIR(colr[i]));
mvwaddstr(cwin->p, pos[NBUSERCOLORS][YPOS] + 1,
pos[NBUSERCOLORS][XPOS] + XOFST, default_txt);
mvwaddstr(cwin->p, pos[SIZE - 1][YPOS] + 1,
pos[SIZE - 1][XPOS] + XOFST, default_txt);
custom_apply_attr(cwin->p, ATTR_HIGHEST);
mvwaddstr(cwin->p, Y, XFORE + XOFST, fore_txt);
mvwaddstr(cwin->p, Y, XBACK + XOFST, back_txt);
custom_remove_attr(cwin->p, ATTR_HIGHEST);
if (colorize) {
mvwaddch(cwin->p, pos[*mark_fore][YPOS],
pos[*mark_fore][XPOS] + 1, MARK);
mvwaddch(cwin->p, pos[*mark_back][YPOS],
pos[*mark_back][XPOS] + 1, MARK);
}
wmove(cwin->p, pos[cursor][YPOS], pos[cursor][XPOS] + 1);
wchgat(cwin->p, 1, A_REVERSE, (colorize ? COLR_CUSTOM : 0), NULL);
color_selection_bar();
wnoutrefresh(win[STA].p);
wnoutrefresh(cwin->p);
wins_doupdate();
if (notify_bar())
notify_update_bar();
}
/* Color theme configuration. */
void custom_color_config(void)
{
struct window conf_win;
int key, cursor, need_reset, theme_changed;
int mark_fore, mark_back;
const char *label = _("color theme");
conf_win.p = 0;
confwin_init(&conf_win, label);
mark_fore = NBUSERCOLORS;
mark_back = SIZE - 1;
cursor = 0;
theme_changed = 0;
display_color_config(&conf_win, &mark_fore, &mark_back, cursor,
theme_changed);
clear();
while ((key = keys_get(win[KEY].p, NULL, NULL)) != KEY_GENERIC_QUIT) {
need_reset = 0;
theme_changed = 0;
switch (key) {
case KEY_GENERIC_SELECT:
colorize = 1;
need_reset = 1;
theme_changed = 1;
if (cursor > NBUSERCOLORS)
mark_back = cursor;
else
mark_fore = cursor;
break;
case KEY_MOVE_DOWN:
if (cursor < SIZE - 1)
++cursor;
break;
case KEY_MOVE_UP:
if (cursor > 0)
--cursor;
break;
case KEY_MOVE_LEFT:
if (cursor > NBUSERCOLORS)
cursor -= (NBUSERCOLORS + 1);
break;
case KEY_MOVE_RIGHT:
if (cursor <= NBUSERCOLORS)
cursor += (NBUSERCOLORS + 1);
break;
case KEY_GENERIC_CANCEL:
colorize = 0;
need_reset = 1;
break;
}
if (resize) {
resize = 0;
endwin();
wins_refresh();
curs_set(0);
need_reset = 1;
}
if (need_reset)
confwin_init(&conf_win, label);
display_color_config(&conf_win, &mark_fore, &mark_back,
cursor, theme_changed);
}
delwin(conf_win.p);
}
enum {
COMPACT_PANELS,
DEFAULT_PANEL,
CAL_VIEW,
TODO_VIEW,
MULTIPLE_DAYS,
HEADER_LINE,
EVENT_SEPARATOR,
DAY_SEPARATOR,
EMPTY_APPT_LINE,
EMPTY_DAY,
AUTO_SAVE,
AUTO_GC,
PERIODIC_SAVE,
SYSTEM_EVENTS,
CONFIRM_QUIT,
CONFIRM_DELETE,
FIRST_DAY_OF_WEEK,
OUTPUT_DATE_FMT,
INPUT_DATE_FMT,
HEADING_POS,
DAY_HEADING_FMT,
APPOINTMENT_TIME_FMT,
NB_OPTIONS
};
/* Prints the general options. */
static void print_general_option(int i, WINDOW *win, int y, int hilt, void *cb_data)
{
const int XPOS = 1;
char *opt[NB_OPTIONS] = {
"appearance.compactpanels = ",
"appearance.defaultpanel = ",
"appearance.calendarview = ",
"appearance.todoview = ",
"general.multipledays = ",
"appearance.headerline = ",
"appearance.eventseparator = ",
"appearance.dayseparator = ",
"appearance.emptyline = ",
"appearance.emptyday = ",
"general.autosave = ",
"general.autogc = ",
"general.periodicsave = ",
"general.systemevents = ",
"general.confirmquit = ",
"general.confirmdelete = ",
"general.firstdayofweek = ",
"format.outputdate = ",
"format.inputdate = ",
"appearance.headingposition = ",
"format.dayheading = ",
"format.appointmenttime = "
};
const char *panel;
const char *position;
if (hilt)
custom_apply_attr(win, ATTR_HIGHEST);
mvwprintw(win, y, XPOS, "%s", opt[i]);
switch (i) {
case COMPACT_PANELS:
print_bool_option_incolor(win, conf.compact_panels, y,
XPOS + strlen(opt[COMPACT_PANELS]));
mvwaddstr(win, y + XPOS, 1,
_("(if set to YES, compact panels are used)"));
break;
case DEFAULT_PANEL:
if (conf.default_panel == CAL)
panel = _("Calendar");
else if (conf.default_panel == APP)
panel = _("Appointments");
else
panel = _("TODO");
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[DEFAULT_PANEL]), panel);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(specifies the panel that is selected by default)"));
break;
case CAL_VIEW:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[CAL_VIEW]),
conf.cal_view == CAL_MONTH_VIEW ?
_("monthly") : _("weekly"));
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS, _("(preferred calendar display)"));
break;
case TODO_VIEW:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[TODO_VIEW]),
conf.todo_view == TODO_SHOW_COMPLETED_VIEW ?
_("show completed") : _("hide completed"));
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS, _("(preferred todo display)"));
break;
case HEADER_LINE:
print_bool_option_incolor(win, conf.header_line, y,
XPOS + strlen(opt[HEADER_LINE]));
mvwaddstr(win, y + XPOS, 1,
_("(horizontal line above the day heading)"));
break;
case EVENT_SEPARATOR:
print_bool_option_incolor(win, conf.event_separator, y,
XPOS + strlen(opt[EVENT_SEPARATOR]));
mvwaddstr(win, y + XPOS, 1,
_("(empty line between events and appointments)"));
break;
case DAY_SEPARATOR:
print_bool_option_incolor(win, conf.day_separator, y,
XPOS + strlen(opt[DAY_SEPARATOR]));
mvwaddstr(win, y + XPOS, 1,
_("(each day ends with an empty line)"));
break;
case EMPTY_APPT_LINE:
print_bool_option_incolor(win, conf.empty_appt_line, y,
XPOS + strlen(opt[EMPTY_APPT_LINE]));
mvwaddstr(win, y + 1, XPOS,
_("(insert an empty line after each appointment)"));
break;
case EMPTY_DAY:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[EMPTY_DAY]),
conf.empty_day);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(text for a day without events and appointments)"));
break;
case MULTIPLE_DAYS:
print_bool_option_incolor(win, conf.multiple_days, y,
XPOS + strlen(opt[MULTIPLE_DAYS]));
mvwaddstr(win, y + 1, XPOS,
_("(display more than one day in the appointments panel)"));
break;
case AUTO_SAVE:
print_bool_option_incolor(win, conf.auto_save, y,
XPOS + strlen(opt[AUTO_SAVE]));
mvwaddstr(win, y + XPOS, 1,
_("(if set to YES, automatic save is done when quitting)"));
break;
case AUTO_GC:
print_bool_option_incolor(win, conf.auto_gc, y,
XPOS + strlen(opt[AUTO_GC]));
mvwaddstr(win, y + 1, XPOS,
_("(run the garbage collector when quitting)"));
break;
case PERIODIC_SAVE:
custom_apply_attr(win, ATTR_HIGHEST);
mvwprintw(win, y, XPOS + strlen(opt[PERIODIC_SAVE]), "%d",
conf.periodic_save);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(if not null, automatically save data every "
"'periodic_save' minutes)"));
break;
case SYSTEM_EVENTS:
print_bool_option_incolor(win, conf.systemevents, y,
XPOS + strlen(opt[SYSTEM_EVENTS]));
mvwaddstr(win, y + 1, XPOS,
_("(if YES, system events are turned into "
"appointments (or else deleted))"));
break;
case CONFIRM_QUIT:
print_bool_option_incolor(win, conf.confirm_quit, y,
XPOS + strlen(opt[CONFIRM_QUIT]));
mvwaddstr(win, y + 1, XPOS,
_("(if set to YES, confirmation is required before quitting)"));
break;
case CONFIRM_DELETE:
print_bool_option_incolor(win, conf.confirm_delete, y,
XPOS + strlen(opt[CONFIRM_DELETE]));
mvwaddstr(win, y + 1, XPOS,
_("(if set to YES, confirmation is required "
"before deleting an event)"));
break;
case FIRST_DAY_OF_WEEK:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[FIRST_DAY_OF_WEEK]),
nl_langinfo(DAY_1 + ui_calendar_get_wday_start()));
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(specifies the first day of week in the calendar view)"));
break;
case OUTPUT_DATE_FMT:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[OUTPUT_DATE_FMT]),
conf.output_datefmt);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(Format of the date to be displayed in non-interactive mode)"));
break;
case INPUT_DATE_FMT:
custom_apply_attr(win, ATTR_HIGHEST);
mvwprintw(win, y, XPOS + strlen(opt[INPUT_DATE_FMT]), "%d",
conf.input_datefmt);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(Format to be used when entering a date: "));
mvwprintw(win, y + 2, XPOS, " (1) %s, (2) %s, (3) %s, (4) %s)",
datefmt_str[0], datefmt_str[1], datefmt_str[2],
datefmt_str[3]);
break;
case HEADING_POS:
if (conf.heading_pos == LEFT)
position = _("to the left");
else if (conf.heading_pos == CENTER)
position = _("in the middle");
else
position = _("to the right");
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[HEADING_POS]), position);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(position of the heading in the appointments panel)"));
break;
case DAY_HEADING_FMT:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[DAY_HEADING_FMT]),
conf.day_heading);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(Format of the date displayed in the appointments panel)"));
break;
case APPOINTMENT_TIME_FMT:
custom_apply_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y, XPOS + strlen(opt[APPOINTMENT_TIME_FMT]),
conf.timefmt);
custom_remove_attr(win, ATTR_HIGHEST);
mvwaddstr(win, y + 1, XPOS,
_("(Format of the time displayed in the appointments panel)"));
break;
}
if (hilt)
custom_remove_attr(win, ATTR_HIGHEST);
}
static enum listbox_row_type general_option_row_type(int i, void *cb_data)
{
return LISTBOX_ROW_TEXT;
}
static int general_option_height(int i, void *cb_data)
{
if (i == INPUT_DATE_FMT)
return 4;
else
return 3;
}
static void general_option_edit(int i)
{
const char *empty_day_str =
_("Enter a text string (an empty string for the default text)");
const char *output_datefmt_str =
_("Enter the date format (see 'man 3 strftime' for possible formats) ");
const char *output_timefmt_str =
_("Enter the time format (see 'man 3 strftime' for possible formats) ");
const char *input_datefmt_prefix = _("Enter the date format: ");
const char *periodic_save_str =
_("Enter the delay, in minutes, between automatic saves (0 to disable) ");
int val;
char *buf;
buf = mem_malloc(BUFSIZ);
buf[0] = '\0';
switch (i) {
case COMPACT_PANELS:
conf.compact_panels = !conf.compact_panels;
resize = 1;
break;
case DEFAULT_PANEL:
if (conf.default_panel == TOD)
conf.default_panel = CAL;
else
conf.default_panel++;
break;
case CAL_VIEW:
if (conf.cal_view == CAL_WEEK_VIEW)
conf.cal_view = CAL_MONTH_VIEW;
else
conf.cal_view++;
ui_calendar_set_view(conf.cal_view);
break;
case TODO_VIEW:
if (conf.todo_view == TODO_HIDE_COMPLETED_VIEW)
conf.todo_view = TODO_SHOW_COMPLETED_VIEW;
else
conf.todo_view++;
ui_todo_set_view(conf.todo_view);
ui_todo_load_items();
break;
case MULTIPLE_DAYS:
conf.multiple_days = !conf.multiple_days;
break;
case HEADER_LINE:
conf.header_line = !conf.header_line;
break;
case EVENT_SEPARATOR:
conf.event_separator = !conf.event_separator;
break;
case DAY_SEPARATOR:
conf.day_separator = !conf.day_separator;
break;
case EMPTY_APPT_LINE:
conf.empty_appt_line = !conf.empty_appt_line;
break;
case EMPTY_DAY:
status_mesg(empty_day_str, "");
strcpy(buf, conf.empty_day);
val = getstring(win[STA].p, buf, 80, 0, 1);
if (val == GETSTRING_VALID)
strcpy(conf.empty_day, buf);
else if (val == GETSTRING_RET)
strcpy(conf.empty_day, EMPTY_DAY_DEFAULT);
break;
case HEADING_POS:
if (conf.heading_pos == RIGHT)
conf.heading_pos = LEFT;
else
conf.heading_pos++;
break;
case AUTO_SAVE:
conf.auto_save = !conf.auto_save;
break;
case AUTO_GC:
conf.auto_gc = !conf.auto_gc;
break;
case PERIODIC_SAVE:
status_mesg(periodic_save_str, "");
snprintf(buf, BUFSIZ, "%d", conf.periodic_save);
if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
val = atoi(buf);
if (val >= 0) {
conf.periodic_save = val;
io_stop_psave_thread();
if (conf.periodic_save > 0)
io_start_psave_thread();
}
}
break;
case SYSTEM_EVENTS:
conf.systemevents = !conf.systemevents;
break;
case CONFIRM_QUIT:
conf.confirm_quit = !conf.confirm_quit;
break;
case CONFIRM_DELETE:
conf.confirm_delete = !conf.confirm_delete;
break;
case FIRST_DAY_OF_WEEK:
ui_calendar_change_first_day_of_week();
ui_calendar_monthly_view_cache_set_invalid();
break;
case OUTPUT_DATE_FMT:
status_mesg(output_datefmt_str, "");
strncpy(buf, conf.output_datefmt, BUFSIZ);
buf[BUFSIZ - 1] = '\0';
if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
strncpy(conf.output_datefmt, buf, BUFSIZ);
conf.output_datefmt[BUFSIZ - 1] = '\0';
}
break;
case INPUT_DATE_FMT:
val = status_ask_simplechoice(input_datefmt_prefix,
datefmt_str,
DATE_FORMATS);
if (val != -1)
conf.input_datefmt = val;
break;
case DAY_HEADING_FMT:
status_mesg(output_datefmt_str, "");
strncpy(buf, conf.day_heading, BUFSIZ);
buf[BUFSIZ - 1] = '\0';
if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
strncpy(conf.day_heading, buf, BUFSIZ);
conf.day_heading[BUFSIZ - 1] = '\0';
}
break;
case APPOINTMENT_TIME_FMT:
status_mesg(output_timefmt_str, "");
strncpy(buf, conf.timefmt, BUFSIZ);
buf[BUFSIZ - 1] = '\0';
if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
strncpy(conf.timefmt, buf, BUFSIZ);
conf.timefmt[BUFSIZ - 1] = '\0';
}
break;
}
mem_free(buf);
}
/* General configuration. */
void custom_general_config(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_EDIT_ITEM
};
struct listbox lb;
int key;
clear();
listbox_init(&lb, 0, 0, notify_bar() ? row - 3 : row - 2, col,
_("general options"), general_option_row_type,
general_option_height, print_general_option);
listbox_load_items(&lb, NB_OPTIONS);
listbox_draw_deco(&lb, 0);
listbox_display(&lb, NOHILT);
wins_set_bindings(bindings, ARRAY_SIZE(bindings));
wins_status_bar();
wnoutrefresh(win[STA].p);
wmove(win[STA].p, 0, 0);
wins_doupdate();
while ((key = keys_get(win[KEY].p, NULL, NULL)) != KEY_GENERIC_QUIT) {
switch (key) {
case KEY_MOVE_DOWN:
listbox_sel_move(&lb, 1);
break;
case KEY_MOVE_UP:
listbox_sel_move(&lb, -1);
break;
case KEY_EDIT_ITEM:
general_option_edit(listbox_get_sel(&lb));
break;
}
if (resize) {
resize = 0;
wins_reset_noupdate();
listbox_resize(&lb, 0, 0, notify_bar() ? row - 3 : row - 2, col);
listbox_draw_deco(&lb, 0);
delwin(win[STA].p);
win[STA].p = newwin(win[STA].h, win[STA].w, win[STA].y, win[STA].x);
keypad(win[STA].p, TRUE);
if (notify_bar()) {
notify_reinit_bar();
notify_update_bar();
}
}
listbox_display(&lb, NOHILT);
wins_status_bar();
wnoutrefresh(win[STA].p);
wmove(win[STA].p, 0, 0);
wins_doupdate();
}
listbox_delete(&lb);
}
static void
print_key_incolor(WINDOW * win, const char *option, int pos_y, int pos_x)
{
const int color = ATTR_HIGHEST;
RETURN_IF(!option, _("Undefined option!"));
custom_apply_attr(win, color);
mvwprintw(win, pos_y, pos_x, "%s ", option);
custom_remove_attr(win, color);
}
static int
print_keys_bindings(WINDOW * win, int selected_row, int selected_elm,
int yoff)
{
const int XPOS = 1;
const int EQUALPOS = 23;
const int KEYPOS = 25;
int noelm, action, y, pos;
const char *key = NULL;
noelm = y = 0;
for (action = 0; action < NBVKEYS; action++) {
char *actionstr;
int nbkeys;
nbkeys = keys_action_count_keys(action);
asprintf(&actionstr, "%s", keys_get_label(action));
if (action == selected_row)
custom_apply_attr(win, ATTR_HIGHEST);
mvwprintw(win, y, XPOS, "%s ", actionstr);
mem_free(actionstr);
mvwaddstr(win, y, EQUALPOS, "=");
if (nbkeys == 0)
mvwaddstr(win, y, KEYPOS, _("UNDEFINED"));
if (action == selected_row)
custom_remove_attr(win, ATTR_HIGHEST);
if (nbkeys > 0) {
if (action == selected_row) {
/* Elements may have been added or deleted. */
wclrtoeol(win);
pos = KEYPOS;
while ((key = keys_action_nkey(action, noelm))) {
if (noelm == selected_elm)
print_key_incolor(win, key,
y, pos);
else
mvwprintw(win, y, pos,
"%s ", key);
noelm++;
pos += utf8_strwidth((char *)key) + 1;
}
} else {
key = keys_action_allkeys(action);
mvwaddstr(win, y, KEYPOS, key);
mem_free((char *)key);
}
}
y += yoff;
}
return noelm;
}
static void custom_keys_config_bar(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_GENERIC_HELP, KEY_ADD_ITEM, KEY_DEL_ITEM,
KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_MOVE_LEFT, KEY_MOVE_RIGHT
};
int bindings_size = ARRAY_SIZE(bindings);
keys_display_bindings_bar(win[STA].p, bindings, bindings_size, 0,
bindings_size);
}
void custom_keys_config(void)
{
struct scrollwin kwin;
int selrow, selelm, firstrow, lastrow, nbrowelm, nbdisplayed;
int ch;
const char *keystr;
WINDOW *grabwin;
const int LINESPERKEY = 2;
const int LABELLINES = 3;
clear();
nbdisplayed = ((notify_bar() ? row - 3 : row - 2) -
LABELLINES) / LINESPERKEY;
wins_scrollwin_init(&kwin, 0, 0, notify_bar() ? row - 3 : row - 2, col,
_("keys configuration"));
wins_scrollwin_set_pad(&kwin, NBVKEYS * LINESPERKEY);
wins_scrollwin_draw_deco(&kwin, 0);
custom_keys_config_bar();
selrow = selelm = 0;
nbrowelm = print_keys_bindings(kwin.inner, selrow, selelm, LINESPERKEY);
wins_scrollwin_display(&kwin, NOHILT);
firstrow = 0;
lastrow = firstrow + nbdisplayed - 1;
for (;;) {
int key = keys_get(win[KEY].p, NULL, NULL);
switch (key) {
case KEY_MOVE_UP:
if (selrow > 0) {
selrow--;
selelm = 0;
if (selrow == firstrow) {
firstrow--;
lastrow--;
wins_scrollwin_up(&kwin,
LINESPERKEY);
}
}
break;
case KEY_MOVE_DOWN:
if (selrow < NBVKEYS - 1) {
selrow++;
selelm = 0;
if (selrow == lastrow) {
firstrow++;
lastrow++;
wins_scrollwin_down(&kwin,
LINESPERKEY);
}
}
break;
case KEY_MOVE_LEFT:
if (selelm > 0)
selelm--;
break;
case KEY_MOVE_RIGHT:
if (selelm < nbrowelm - 1)
selelm++;
break;
case KEY_GENERIC_HELP:
keys_popup_info(selrow);
break;
case KEY_ADD_ITEM:
#define WINROW 10
#define WINCOL 50
grabwin = popup(WINROW, WINCOL,
(row - WINROW) / 2, (col - WINCOL) / 2,
_("Press the key you want to assign to:"),
keys_get_label(selrow), 0);
for (;;) {
ch = keys_wgetch(grabwin);
enum vkey action = keys_get_action(ch);
/* Is the key already used by this action? */
if (action == selrow)
break;
/* Is the key used by another action? */
if (keys_assign_binding(ch, selrow)) {
char *keystr = keys_int2str(ch);
WARN_MSG(_("The key '%s' is already used for %s. "
"Choose another one."),
keystr,
keys_get_label(action));
mem_free(keystr);
werase(kwin.inner);
nbrowelm =
print_keys_bindings(kwin.inner,
selrow,
selelm,
LINESPERKEY);
wins_scrollwin_display(&kwin, NOHILT);
wins_redrawwin(grabwin);
continue;
}
nbrowelm++;
selelm = nbrowelm - 1;
break;
}
delwin(grabwin);
#undef WINROW
#undef WINCOL
break;
case KEY_DEL_ITEM:
keystr = keys_action_nkey(selrow, selelm);
ch = keys_str2int(keystr);
keys_remove_binding(ch, selrow);
nbrowelm--;
if (selelm > 0 && selelm >= nbrowelm)
selelm--;
break;
case KEY_GENERIC_QUIT:
if (keys_check_undefined())
WARN_MSG(_("Some actions are left undefined!"));
wins_scrollwin_delete(&kwin);
return;
}
if (resize) {
resize = 0;
wins_reset_noupdate();
nbdisplayed = ((notify_bar() ? row - 3 : row - 2) -
LABELLINES) / LINESPERKEY;
lastrow = firstrow + nbdisplayed - 1;
wins_scrollwin_resize(&kwin, 0, 0,
notify_bar() ? row - 3 : row - 2, col);
wins_scrollwin_draw_deco(&kwin, 0);
delwin(win[STA].p);
win[STA].p = newwin(win[STA].h, win[STA].w, win[STA].y,
win[STA].x);
keypad(win[STA].p, TRUE);
if (notify_bar()) {
notify_reinit_bar();
notify_update_bar();
}
}
custom_keys_config_bar();
werase(kwin.inner);
nbrowelm =
print_keys_bindings(kwin.inner, selrow, selelm,
LINESPERKEY);
wins_scrollwin_display(&kwin, NOHILT);
}
}
void custom_config_main(void)
{
static int bindings[] = {
KEY_GENERIC_QUIT, KEY_CONFIGMENU_GENERAL,
KEY_CONFIGMENU_LAYOUT, KEY_CONFIGMENU_SIDEBAR,
KEY_CONFIGMENU_COLOR, KEY_CONFIGMENU_NOTIFY,
KEY_CONFIGMENU_KEYS
};
const char *no_color_support =
_("Sorry, colors are not supported by your terminal\n"
"(Press [ENTER] to continue)");
int ch;
int old_layout;
wins_set_bindings(bindings, ARRAY_SIZE(bindings));
wins_update_border(FLAG_ALL);
wins_update_panels(FLAG_ALL);
wins_status_bar();
if (notify_bar())
notify_update_bar();
wmove(win[STA].p, 0, 0);
wins_doupdate();
while (1) {
ch = keys_wgetch(win[KEY].p);
if (keys_get_action(ch) == KEY_GENERIC_QUIT)
break;
switch (ch) {
case 'C':
case 'c':
if (has_colors()) {
custom_color_config();
} else {
colorize = 0;
wins_erase_status_bar();
mvwaddstr(win[STA].p, 0, 0, no_color_support);
keys_wait_for_any_key(win[KEY].p);
}
break;
case 'L':
case 'l':
old_layout = wins_layout();
custom_layout_config();
if (wins_layout() != old_layout)
wins_resize();
break;
case 'G':
case 'g':
custom_general_config();
break;
case 'N':
case 'n':
notify_config_bar();
break;
case 'K':
case 'k':
custom_keys_config();
break;
case 's':
case 'S':
custom_sidebar_config();
break;
default:
break;
}
if (resize) {
resize = 0;
wins_reset();
}
/* needed to update app list */
day_do_storage(0);
/* wins_update(FLAG_ALL), but with custom bindings */
wins_set_bindings(bindings, ARRAY_SIZE(bindings));
wins_update_border(FLAG_ALL);
wins_update_panels(FLAG_ALL);
wins_status_bar();
if (notify_bar())
notify_update_bar();
wmove(win[STA].p, 0, 0);
wins_doupdate();
}
if (!config_save())
EXIT(_("Could not save %s."), path_conf);
if (!io_save_keys())
EXIT(_("Could not save %s."), path_keys);
}