Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

392 lines (359 sloc) 16.96 kb
#include <keymap.h>
#include <logger.h>
#include <vector>
#include <iostream>
#include <config.h>
#include <exceptions.h>
#include <utils.h>
namespace newsbeuter {
struct op_desc {
operation op;
const char * opstr;
const char * default_key;
const char * help_text;
unsigned short flags;
};
/*
* This is the list of operations, defining operation, operation name (for keybindings), default key, description, and where it's valid
*/
static op_desc opdescs[] = {
{ OP_OPEN, "open", "ENTER", _("Open feed/article"), KM_FEEDLIST | KM_FILEBROWSER | KM_HELP | KM_ARTICLELIST | KM_TAGSELECT | KM_FILTERSELECT | KM_URLVIEW | KM_PODBEUTER | KM_DIALOGS },
{ OP_QUIT, "quit", "q", _("Return to previous dialog/Quit"), KM_FEEDLIST | KM_FILEBROWSER | KM_HELP | KM_ARTICLELIST | KM_ARTICLE | KM_TAGSELECT | KM_FILTERSELECT | KM_URLVIEW | KM_PODBEUTER | KM_DIALOGS },
{ OP_HARDQUIT, "hard-quit", "Q", _("Quit program, no confirmation"), KM_FEEDLIST | KM_FILEBROWSER | KM_HELP | KM_ARTICLELIST | KM_ARTICLE | KM_TAGSELECT | KM_FILTERSELECT | KM_URLVIEW | KM_PODBEUTER | KM_DIALOGS },
{ OP_RELOAD, "reload", "r", _("Reload currently selected feed"), KM_FEEDLIST },
{ OP_RELOADALL, "reload-all", "R", _("Reload all feeds"), KM_FEEDLIST },
{ OP_MARKFEEDREAD, "mark-feed-read", "A", _("Mark feed read"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_MARKALLFEEDSREAD, "mark-all-feeds-read", "C", _("Mark all feeds read"), KM_FEEDLIST },
{ OP_SAVE, "save", "s", _("Save article"), KM_ARTICLELIST | KM_ARTICLE },
{ OP_NEXT, "next", "J", _("Go to next article"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_PREV, "prev", "K", _("Go to previous article"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_NEXTUNREAD, "next-unread", "n", _("Go to next unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_PREVUNREAD, "prev-unread", "p", _("Go to previous unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_RANDOMUNREAD, "random-unread", "^K", _("Go to a random unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_OPENBROWSER_AND_MARK, "open-in-browser-and-mark-read", "O", _("Open article in browser and mark read"), KM_ARTICLELIST },
{ OP_OPENINBROWSER, "open-in-browser", "o", _("Open article in browser"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE },
{ OP_HELP, "help", "?", _("Open help dialog"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE | KM_PODBEUTER },
{ OP_TOGGLESOURCEVIEW, "toggle-source-view", "^U", _("Toggle source view"), KM_ARTICLE },
{ OP_TOGGLEITEMREAD, "toggle-article-read", "N", _("Toggle read status for article"), KM_ARTICLELIST | KM_ARTICLE },
{ OP_TOGGLESHOWREAD, "toggle-show-read-feeds", "l", _("Toggle show read feeds/articles"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_SHOWURLS, "show-urls", "u", _("Show URLs in current article"), KM_ARTICLE | KM_ARTICLELIST },
{ OP_CLEARTAG, "clear-tag", "^T", _("Clear current tag"), KM_FEEDLIST },
{ OP_SETTAG, "set-tag", "t", _("Select tag"), KM_FEEDLIST },
{ OP_SETTAG, "select-tag", "t", _("Select tag"), KM_FEEDLIST },
{ OP_SEARCH, "open-search", "/", _("Open search dialog"), KM_FEEDLIST | KM_HELP | KM_ARTICLELIST | KM_ARTICLE },
{ OP_GOTO_URL, "goto-url", "#", _("Goto URL #"), KM_ARTICLE },
{ OP_ENQUEUE, "enqueue", "e", _("Add download to queue"), KM_ARTICLE },
{ OP_RELOADURLS, "reload-urls", "^R", _("Reload the list of URLs from the configuration"), KM_FEEDLIST },
{ OP_PB_DOWNLOAD, "pb-download", "d", _("Download file"), KM_PODBEUTER },
{ OP_PB_CANCEL, "pb-cancel", "c", _("Cancel download"), KM_PODBEUTER },
{ OP_PB_DELETE, "pb-delete", "D", _("Mark download as deleted"), KM_PODBEUTER },
{ OP_PB_PURGE, "pb-purge", "P", _("Purge finished and deleted downloads from queue"), KM_PODBEUTER },
{ OP_PB_TOGGLE_DLALL, "pb-toggle-download-all", "a", _("Toggle automatic download on/off"), KM_PODBEUTER },
{ OP_PB_PLAY, "pb-play", "p", _("Start player with currently selected download"), KM_PODBEUTER },
{ OP_PB_MARK_FINISHED, "pb-mark-as-finished", "m", _("Mark file as finished (not played)"), KM_PODBEUTER },
{ OP_PB_MOREDL, "pb-increase-max-dls", "+", _("Increase the number of concurrent downloads"), KM_PODBEUTER },
{ OP_PB_LESSDL, "pb-decreate-max-dls", "-", _("Decrease the number of concurrent downloads"), KM_PODBEUTER },
{ OP_REDRAW, "redraw", "^L", _("Redraw screen"), KM_SYSKEYS },
{ OP_CMDLINE, "cmdline", ":", _("Open the commandline"), KM_NEWSBEUTER },
{ OP_SETFILTER, "set-filter", "F", _("Set a filter"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_SELECTFILTER, "select-filter", "f", _("Select a predefined filter"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_CLEARFILTER, "clear-filter", "^F", _("Clear currently set filter"), KM_FEEDLIST | KM_HELP | KM_ARTICLELIST },
{ OP_BOOKMARK, "bookmark", "^B", _("Bookmark current link/article"), KM_ARTICLELIST | KM_ARTICLE | KM_URLVIEW },
{ OP_EDITFLAGS, "edit-flags", "^E", _("Edit flags"), KM_ARTICLELIST | KM_ARTICLE },
{ OP_NEXTFEED, "next-feed", "j", _("Go to next feed"), KM_ARTICLELIST },
{ OP_PREVFEED, "prev-feed", "k", _("Go to previous feed"), KM_ARTICLELIST },
{ OP_NEXTUNREADFEED, "next-unread-feed", "^N", _("Go to next unread feed"), KM_ARTICLELIST },
{ OP_PREVUNREADFEED, "prev-unread-feed", "^P", _("Go to previous unread feed"), KM_ARTICLELIST },
{ OP_MACROPREFIX, "macro-prefix", ",", _("Call a macro"), KM_NEWSBEUTER },
{ OP_DELETE, "delete-article", "D", _("Delete article"), KM_ARTICLELIST | KM_ARTICLE },
{ OP_PURGE_DELETED, "purge-deleted", "$", _("Purge deleted articles"), KM_ARTICLELIST },
{ OP_EDIT_URLS, "edit-urls", "E", _("Edit subscribed URLs"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_CLOSEDIALOG, "close-dialog", "^X", _("Close currently selected dialog"), KM_DIALOGS },
{ OP_VIEWDIALOGS, "view-dialogs", "v", _("View list of open dialogs"), KM_NEWSBEUTER },
{ OP_NEXTDIALOG, "next-dialog", "^V", _("Go to next dialog"), KM_NEWSBEUTER },
{ OP_PREVDIALOG, "prev-dialog", "^G", _("Go to previous dialog"), KM_NEWSBEUTER },
{ OP_PIPE_TO, "pipe-to", "|", _("Pipe article to command"), KM_ARTICLE | KM_ARTICLELIST },
{ OP_SORT, "sort", "g", _("Sort current list"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_REVSORT, "rev-sort", "G", _("Sort current list (reverse)"), KM_FEEDLIST | KM_ARTICLELIST },
{ OP_0, "zero", "0", _("Open URL 10"), KM_URLVIEW | KM_ARTICLE },
{ OP_1, "one", "1", _("Open URL 1"), KM_URLVIEW | KM_ARTICLE},
{ OP_2, "two", "2", _("Open URL 2"), KM_URLVIEW | KM_ARTICLE},
{ OP_3, "three","3", _("Open URL 3"), KM_URLVIEW | KM_ARTICLE},
{ OP_4, "four", "4", _("Open URL 4"), KM_URLVIEW | KM_ARTICLE},
{ OP_5, "five", "5", _("Open URL 5"), KM_URLVIEW | KM_ARTICLE},
{ OP_6, "six", "6", _("Open URL 6"), KM_URLVIEW | KM_ARTICLE},
{ OP_7, "seven","7", _("Open URL 7"), KM_URLVIEW | KM_ARTICLE},
{ OP_8, "eight","8", _("Open URL 8"), KM_URLVIEW | KM_ARTICLE},
{ OP_9, "nine", "9", _("Open URL 9"), KM_URLVIEW | KM_ARTICLE},
{ OP_SK_UP, "up", "UP", _("Move to the previous entry"), KM_SYSKEYS },
{ OP_SK_DOWN, "down", "DOWN", _("Move to the next entry"), KM_SYSKEYS },
{ OP_SK_PGUP, "pageup", "PAGEUP", _("Move to the previous page"), KM_SYSKEYS },
{ OP_SK_PGDOWN, "pagedown", "PAGEDOWN", _("Move to the next page"), KM_SYSKEYS },
{ OP_SK_HOME, "home", "HOME", _("Move to the start of page/list"), KM_SYSKEYS },
{ OP_SK_END, "end", "END", _("Move to the end of page/list"), KM_SYSKEYS },
{ OP_INT_END_QUESTION, "XXXNOKEY-end-question", "end-question", NULL, KM_INTERNAL },
{ OP_INT_CANCEL_QNA, "XXXNOKEY-cancel-qna", "cancel-qna", NULL, KM_INTERNAL },
{ OP_INT_QNA_NEXTHIST, "XXXNOKEY-qna-next-history", "qna-next-history", NULL, KM_INTERNAL },
{ OP_INT_QNA_PREVHIST, "XXXNOKEY-qna-prev-history", "qna-prev-history", NULL, KM_INTERNAL },
{ OP_INT_RESIZE, "RESIZE", "internal-resize", NULL, KM_INTERNAL },
{ OP_INT_SET, "set", "internal-set", NULL, KM_INTERNAL },
{ OP_INT_GOTO_URL, "gotourl", "internal-goto-url", NULL, KM_INTERNAL },
{ OP_NIL, NULL, NULL, NULL, 0 }
};
// "all" must be first, the following positions must be the same as the KM_* flag definitions (get_flag_from_context() relies on this).
static const char * contexts[] = { "all", "feedlist", "filebrowser", "help", "articlelist", "article", "tagselection", "filterselection", "urlview", "podbeuter", "dialogs", NULL };
keymap::keymap(unsigned flags) {
/*
* At startup, initialize the keymap with the default settings from the list above.
*/
LOG(LOG_DEBUG, "keymap::keymap: flags = %x", flags);
for (unsigned int j=1;contexts[j]!=NULL;j++) {
std::string ctx(contexts[j]);
for (int i=0;opdescs[i].op != OP_NIL;++i) {
if (opdescs[i].flags & (flags | KM_INTERNAL | KM_SYSKEYS)) {
keymap_[ctx][opdescs[i].default_key] = opdescs[i].op;
}
}
}
}
void keymap::get_keymap_descriptions(std::vector<keymap_desc>& descs, unsigned short flags) {
/*
* Here we return the keymap descriptions for the specified application (handed to us via flags)
* This is used for the help screen.
*/
for (unsigned int i=1;contexts[i]!=NULL;i++) {
std::string ctx(contexts[i]);
if (flags & KM_PODBEUTER && ctx != "podbeuter") {
continue;
} else if (flags & KM_NEWSBEUTER && ctx == "podbeuter") {
continue;
}
for (unsigned int j=0;opdescs[j].op != OP_NIL;++j) {
bool already_added = false;
for (std::map<std::string,operation>::iterator it=keymap_[ctx].begin();it!=keymap_[ctx].end();++it) {
operation op = it->second;
if (op != OP_NIL) {
if (opdescs[j].op == op && opdescs[j].flags & flags) {
keymap_desc desc;
desc.key = it->first;
desc.ctx = ctx;
if (!already_added) {
desc.cmd = opdescs[j].opstr;
if (opdescs[j].help_text)
desc.desc = gettext(opdescs[j].help_text);
already_added = true;
}
desc.flags = opdescs[j].flags;
descs.push_back(desc);
}
}
}
if (!already_added) {
if (opdescs[j].flags & flags) {
LOG(LOG_DEBUG, "keymap::get_keymap_descriptions: found unbound function: %s ctx = %s", opdescs[j].opstr, ctx.c_str());
keymap_desc desc;
desc.ctx = ctx;
desc.cmd = opdescs[j].opstr;
if (opdescs[j].help_text)
desc.desc = gettext(opdescs[j].help_text);
desc.flags = opdescs[j].flags;
descs.push_back(desc);
}
}
}
}
}
keymap::~keymap() { }
void keymap::set_key(operation op, const std::string& key, const std::string& context) {
LOG(LOG_DEBUG,"keymap::set_key(%d,%s) called", op, key.c_str());
if (context == "all") {
for (unsigned int i=0;contexts[i]!=NULL;i++) {
keymap_[contexts[i]][key] = op;
}
} else {
keymap_[context][key] = op;
}
}
void keymap::unset_key(const std::string& key, const std::string& context) {
LOG(LOG_DEBUG,"keymap::unset_key(%s) called", key.c_str());
if (context == "all") {
for (unsigned int i=0;contexts[i]!=NULL;i++) {
keymap_[contexts[i]][key] = OP_NIL;
}
} else {
keymap_[context][key] = OP_NIL;
}
}
operation keymap::get_opcode(const std::string& opstr) {
for (int i=0;opdescs[i].opstr;++i) {
if (opstr == opdescs[i].opstr) {
return opdescs[i].op;
}
}
return OP_NIL;
}
char keymap::get_key(const std::string& keycode) {
if (keycode == "ENTER") {
return '\n';
} else if (keycode == "ESC") {
return 27;
} else if (keycode.length() == 2 && keycode[0] == '^') {
char chr = keycode[1];
return chr - '@';
} else if (keycode.length() == 1) { // TODO: implement more keys
return keycode[0];
}
return 0;
}
operation keymap::get_operation(const std::string& keycode, const std::string& context) {
std::string key;
LOG(LOG_DEBUG, "keymap::get_operation: keycode = %s context = %s", keycode.c_str(), context.c_str());
if (keycode.length() > 0) {
key = keycode;
} else {
key = "NIL";
}
return keymap_[context][key];
}
void keymap::dump_config(std::vector<std::string>& config_output) {
for (unsigned int i=1;contexts[i]!=NULL;i++) { // TODO: optimize
std::map<std::string,operation>& x = keymap_[contexts[i]];
for (std::map<std::string,operation>::iterator it = x.begin();it!=x.end();++it) {
if (it->second < OP_INT_MIN) {
std::string configline = "bind-key ";
configline.append(it->first);
configline.append(" ");
configline.append(getopname(it->second));
configline.append(" ");
configline.append(contexts[i]);
config_output.push_back(configline);
}
}
}
for (std::map<std::string,std::vector<macrocmd> >::iterator it=macros_.begin();it!=macros_.end();++it) {
std::string configline = "macro ";
configline.append(it->first);
configline.append(" ");
unsigned int i=0;
for (std::vector<macrocmd>::iterator jt=it->second.begin();jt!=it->second.end();++jt,i++) {
configline.append(getopname(jt->op));
for (std::vector<std::string>::iterator kt=jt->args.begin();kt!=jt->args.end();++kt) {
configline.append(" ");
configline.append(utils::quote(*kt));
}
if (i < (it->second.size()-1))
configline.append(" ; ");
}
config_output.push_back(configline);
}
}
std::string keymap::getopname(operation op) {
for (unsigned int i=0;opdescs[i].op != OP_NIL;i++) {
if (opdescs[i].op == op)
return opdescs[i].opstr;
}
return "<none>";
}
void keymap::handle_action(const std::string& action, const std::vector<std::string>& params) {
/*
* The keymap acts as config_action_handler so that all the key-related configuration is immediately
* handed to it.
*/
LOG(LOG_DEBUG,"keymap::handle_action(%s, ...) called",action.c_str());
if (action == "bind-key") {
if (params.size() < 2)
throw confighandlerexception(AHS_TOO_FEW_PARAMS);
std::string context = "all";
if (params.size() >= 3)
context = params[2];
if (!is_valid_context(context))
throw confighandlerexception(utils::strprintf(_("`%s' is not a valid context"), context.c_str()));
operation op = get_opcode(params[1]);
if (op > OP_SK_MIN && op < OP_SK_MAX)
unset_key(getkey(op, context), context);
set_key(op, params[0], context);
} else if (action == "unbind-key") {
if (params.size() < 1)
throw confighandlerexception(AHS_TOO_FEW_PARAMS);
std::string context = "all";
if (params.size() >= 2)
context = params[1];
unset_key(params[0], context);
} else if (action == "macro") {
if (params.size() < 1)
throw confighandlerexception(AHS_TOO_FEW_PARAMS);
std::vector<std::string>::const_iterator it = params.begin();
std::string macrokey = *it;
std::vector<macrocmd> cmds;
macrocmd tmpcmd;
tmpcmd.op = OP_NIL;
bool first = true;
++it;
while (it != params.end()) {
if (first && *it != ";") {
tmpcmd.op = get_opcode(*it);
LOG(LOG_DEBUG, "keymap::handle_action: new operation `%s' (op = %u)", it->c_str(), tmpcmd.op);
if (tmpcmd.op == OP_NIL)
throw confighandlerexception(utils::strprintf(_("`%s' is not a valid key command"), it->c_str()));
first = false;
} else {
if (*it == ";") {
if (tmpcmd.op != OP_NIL)
cmds.push_back(tmpcmd);
tmpcmd.op = OP_NIL;
tmpcmd.args.clear();
first = true;
} else {
LOG(LOG_DEBUG, "keymap::handle_action: new parameter `%s' (op = %u)", it->c_str());
tmpcmd.args.push_back(*it);
}
}
++it;
}
if (tmpcmd.op != OP_NIL)
cmds.push_back(tmpcmd);
macros_[macrokey] = cmds;
} else
throw confighandlerexception(AHS_INVALID_PARAMS);
}
std::string keymap::getkey(operation op, const std::string& context) {
if (context == "all") {
for (unsigned int i=0;contexts[i]!=NULL;i++) {
std::string ctx(contexts[i]);
for (std::map<std::string,operation>::iterator it=keymap_[ctx].begin(); it!=keymap_[ctx].end(); ++it) {
if (it->second == op)
return it->first;
}
}
} else {
for (std::map<std::string,operation>::iterator it=keymap_[context].begin(); it!=keymap_[context].end(); ++it) {
if (it->second == op)
return it->first;
}
}
return "<none>";
}
std::vector<macrocmd> keymap::get_macro(const std::string& key) {
for (std::map<std::string,std::vector<macrocmd> >::iterator it=macros_.begin(); it!=macros_.end(); ++it) {
if (it->first == key) {
return it->second;
}
}
std::vector<macrocmd> dummyvector;
return dummyvector;
}
bool keymap::is_valid_context(const std::string& context) {
for (unsigned int i=0;contexts[i]!=NULL;i++) {
if (context == contexts[i])
return true;
}
return false;
}
unsigned short keymap::get_flag_from_context(const std::string& context) {
for (unsigned int i=1;contexts[i]!=NULL;i++) {
if (context == contexts[i])
return (1<<(i-1)) | KM_SYSKEYS;
}
return 0; // shouldn't happen
}
}
Jump to Line
Something went wrong with that request. Please try again.