#include #include #include #include #include #include #include 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_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_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_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_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_ARTICLELIST }, { 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_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-unread-feed", "^N", _("Go to next unread feed"), KM_ARTICLELIST }, { OP_PREVFEED, "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 }, { 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& 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::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& config_output) { for (unsigned int i=1;contexts[i]!=NULL;i++) { // TODO: optimize std::map& x = keymap_[contexts[i]]; for (std::map::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 >::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::iterator jt=it->second.begin();jt!=it->second.end();jt++,i++) { configline.append(getopname(jt->op)); for (std::vector::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 ""; } void keymap::handle_action(const std::string& action, const std::vector& 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::const_iterator it = params.begin(); std::string macrokey = *it; std::vector 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::iterator it=keymap_[ctx].begin(); it!=keymap_[ctx].end(); ++it) { if (it->second == op) return it->first; } } } else { for (std::map::iterator it=keymap_[context].begin(); it!=keymap_[context].end(); ++it) { if (it->second == op) return it->first; } } return ""; } std::vector keymap::get_macro(const std::string& key) { for (std::map >::iterator it=macros_.begin(); it!=macros_.end(); ++it) { if (it->first == key) { return it->second; } } std::vector 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 } }