xaizek / pms (License: GPLv3+) (since 2018-12-07)
Older version of Practical Music Search written in C++.
<root> / src / keybinding.cpp (5c365e6b692bb35257cf2a72d16dbaaf6844c2fd) (8,542B) (mode 100644) [raw]
/* vi:set ts=8 sts=8 sw=8 noet:
 *
 * Practical Music Search
 * Copyright (c) 2006-2011  Kim Tore Jensen
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "input.h"
#include "command.h"
#include "console.h"
#include "curses.h"
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

void Keybindings::load_defaults()
{
	truncate();

	add(CONTEXT_ALL, ACT_MODE_INPUT, ":");
	add(CONTEXT_SONGLIST, ACT_MODE_LIVESEARCH, "/");
	add(CONTEXT_SONGLIST, ACT_MODE_SEARCH, "<C-f>");
	add(CONTEXT_SONGLIST, ACT_RESET_SEARCH, "<C-g>");
	add(CONTEXT_ALL, ACT_REPEATACTION, ".");
	add(CONTEXT_ALL, ACT_QUIT, "q");
	add(CONTEXT_ALL, ACT_UPDATE, "U");

	add(CONTEXT_ALL, ACT_NEXT_WINDOW, "l");
	add(CONTEXT_ALL, ACT_NEXT_WINDOW, "gt");
	add(CONTEXT_ALL, ACT_PREVIOUS_WINDOW, "h");
	add(CONTEXT_ALL, ACT_PREVIOUS_WINDOW, "gT");
	add(CONTEXT_ALL, ACT_TOGGLE_WINDOW, "<Tab>");
	add(CONTEXT_ALL, ACT_GOTO_WINDOW_POS, "B");

	add(CONTEXT_SONGLIST, ACT_ADD, "a");
	add(CONTEXT_SONGLIST, ACT_ADD_SAME, "b", "album");
	add(CONTEXT_SONGLIST, ACT_REMOVE, "d");
	add(CONTEXT_SONGLIST, ACT_REMOVE, "<Delete>");
	add(CONTEXT_SONGLIST, ACT_REMOVE, "x");
	add(CONTEXT_SONGLIST, ACT_VISUAL, "v");
	add(CONTEXT_SONGLIST, ACT_VISUAL, "V");
	add(CONTEXT_SONGLIST, ACT_YANK, "y");
	add(CONTEXT_SONGLIST, ACT_YANK, "Y");
	add(CONTEXT_SONGLIST, ACT_PUT, "p");

	add(CONTEXT_LIST, ACT_SCROLL_UP, "<C-y>");
	add(CONTEXT_LIST, ACT_SCROLL_DOWN, "<C-e>");
	add(CONTEXT_LIST, ACT_CURSOR_UP, "k");
	add(CONTEXT_LIST, ACT_CURSOR_UP, "<Up>");
	add(CONTEXT_LIST, ACT_CURSOR_PGUP, "<PgUp>");
	add(CONTEXT_LIST, ACT_CURSOR_TOP, "H");
	add(CONTEXT_LIST, ACT_CURSOR_BOTTOM, "L");
	add(CONTEXT_LIST, ACT_CURSOR_DOWN, "j");
	add(CONTEXT_LIST, ACT_CURSOR_DOWN, "<Down>");
	add(CONTEXT_LIST, ACT_CURSOR_PGDOWN, "<PgDn>");
	add(CONTEXT_LIST, ACT_CURSOR_HOME, "gg");
	add(CONTEXT_LIST, ACT_CURSOR_HOME, "<Home>");
	add(CONTEXT_LIST, ACT_CURSOR_END, "G");
	add(CONTEXT_LIST, ACT_CURSOR_END, "<End>");
	add(CONTEXT_LIST, ACT_CURSOR_CURRENTSONG, "C");
	add(CONTEXT_LIST, ACT_CURSOR_CURRENTSONG, "gc");
	add(CONTEXT_LIST, ACT_CURSOR_RANDOM, "R");
	add(CONTEXT_LIST, ACT_CURSOR_RANDOM, "gr");

	add(CONTEXT_ALL, ACT_SET, "c", "consume!");
	add(CONTEXT_ALL, ACT_SET, "z", "random!");
	add(CONTEXT_ALL, ACT_SET, "r", "repeat!");
	add(CONTEXT_ALL, ACT_SET, "s", "single!");
	add(CONTEXT_ALL, ACT_SET, "+", "volume+=2");
	add(CONTEXT_ALL, ACT_SET, "-", "volume-=2");
	add(CONTEXT_ALL, ACT_SET, "m", "mute!");

	add(CONTEXT_ALL, ACT_TOGGLEPLAY, "<Space>");
	add(CONTEXT_SONGLIST, ACT_PLAY, "<Enter>");
	add(CONTEXT_ALL, ACT_STOP, "<Backspace>");
	add(CONTEXT_ALL, ACT_NEXT, "<Right>");
	add(CONTEXT_ALL, ACT_PREVIOUS, "<Left>");
	add(CONTEXT_ALL, ACT_SEEK_FORWARD, "\\>");
	add(CONTEXT_ALL, ACT_SEEK_BACK, "\\<");
}

Keybinding * Keybindings::add(int context, action_t action, string sequence, string params)
{
	Keybinding * c;
	vector<int> * seq;

	seq = conv_sequence(sequence);
	if (!seq)
	{
		return NULL;
	}
	if ((c = find_conflict(seq)))
	{
		sterr("Key binding '%s' conflicts with already configured '%s'.", sequence.c_str(), c->seqstr.c_str());
		return NULL;
	}

	c = new Keybinding();
	c->context = context;
	c->action = action;
	c->sequence = *seq;
	c->seqstr = sequence;
	c->params = params;
	bindings.push_back(c);
	return c;
}

vector<int> * Keybindings::conv_sequence(string seq)
{
	vector<int> * r;
	string subseq;
	size_t i;
	size_t epos;
	int escape = false;
	int ex = false;
	int ctrl = false;

	r = new vector<int>;

	for (i = 0; i < seq.size(); i++)
	{
		/* Escape sequence */
		if (seq[i] == '\\')
		{
			if (escape)
				r->push_back(seq[i]);
			escape = !escape;
			continue;
		}
		/* Start a special key */
		else if (seq[i] == '<')
		{
			if (escape)
			{
				escape = false;
				r->push_back(seq[i]);
				continue;
			}
			if (ex)
			{
				sterr("Bind: unexpected '%c' near ...%s, declaration dropped.", seq[i], seq.substr(i - 1).c_str());
				delete r;
				return NULL;
			}
			ex = true;
		}
		/* End a special key */
		else if (seq[i] == '>')
		{
			if (escape)
			{
				escape = false;
				r->push_back(seq[i]);
				continue;
			}
			if (!ex)
			{
				sterr("Bind: unexpected '%c' near ...%s, declaration dropped.", seq[i], seq.substr(i).c_str());
				delete r;
				return NULL;
			}
			ex = false;
		}
		else
		{
			/* Just a normal character */
			if (!ex)
			{
				r->push_back(seq[i]);
				continue;
			}
			else if (ctrl)
			{
				seq[i] = ::toupper(seq[i]);
				if (!(seq[i] >= 'A' || seq[i] <= 'Z'))
				{
					sterr("Bind: unexpected %c, expected one letter between A-Z near ...%s, declaration dropped.", seq[i], seq.substr(i - 3).c_str());
					delete r;
					return NULL;
				}
				r->push_back(seq[i] - 64);
				ctrl = false;
				continue;
			}

			/* Switch on Ctrl-mode */
			if (seq.substr(i, 2) == "C-")
			{
				ctrl = true;
				++i;
				continue;
			}

			epos = seq.find('>', i);
			if (epos == string::npos)
			{
				sterr("Bind: unclosed tag near ...%s, declaration dropped.", seq.substr(i - 1).c_str());
				delete r;
				return NULL;
			}

			subseq = seq.substr(i, epos - i);
			std::transform(subseq.begin(), subseq.end(), subseq.begin(), ::tolower);
			if (subseq == "up")
				r->push_back(KEY_UP);
			else if (subseq == "down")
				r->push_back(KEY_DOWN);
			else if (subseq == "left")
				r->push_back(KEY_LEFT);
			else if (subseq == "right")
				r->push_back(KEY_RIGHT);
			else if (subseq == "pgup")
				r->push_back(KEY_PPAGE);
			else if (subseq == "pgdn")
				r->push_back(KEY_NPAGE);
			else if (subseq == "home")
				r->push_back(KEY_HOME);
			else if (subseq == "end")
				r->push_back(KEY_END);
			else if (subseq == "backspace")
				r->push_back(KEY_BACKSPACE);
			else if (subseq == "delete")
				r->push_back(KEY_DC);
			else if (subseq == "insert")
				r->push_back(KEY_IC);
			else if (subseq == "cr" || subseq == "return" || subseq == "enter")
				r->push_back(KEY_ENTER);
			else if (subseq == "space")
				r->push_back(' ');
			else if (subseq == "tab")
				r->push_back('\t');

			else
			{
				sterr("Bind: invalid identifier '<%s>', declaration dropped.", subseq.c_str());
				delete r;
				return NULL;
			}

			i = epos - 1;
		}
	}

	return r;
}

Keybinding * Keybindings::find_conflict(vector<int> * sequence)
{
	vector<Keybinding *>::iterator i;
	unsigned int s;
	unsigned int limit;

	for (i = bindings.begin(); i != bindings.end(); i++)
	{
		limit = sequence->size() > (*i)->sequence.size() ? (*i)->sequence.size() : sequence->size();
		for (s = 0; s < limit; ++s)
		{
			if (sequence->at(s) != (*i)->sequence.at(s))
				break;
		}
		if (s == limit)
			return *i;
	}

	return NULL;
}

bool Keybindings::remove(string sequence)
{
	vector<Keybinding *>::iterator it;
	Keybinding * c;
	vector<int> * seq;

	if ((seq = conv_sequence(sequence)) == NULL)
		return false;

	c = find_conflict(seq);
	delete seq;

	if (c)
	for (it = bindings.begin(); it != bindings.end(); ++it)
	{
		if (*it == c)
		{
			debug("Removed key mapping `%s'.", sequence.c_str());
			delete c;
			bindings.erase(it);
			return true;
		}
	}

	sterr("Key sequence `%s' is not mapped.", sequence.c_str());
	return false;
}

int Keybindings::find(int context, vector<int> * sequence, action_t * action, string * params)
{
	vector<Keybinding *>::iterator i;
	int found = KEYBIND_FIND_NOMATCH;
	unsigned int s;

	for (i = bindings.begin(); i != bindings.end(); i++)
	{
		if (!((*i)->context & context) || (*i)->sequence.size() < sequence->size())
			continue;

		for (s = 0; s < sequence->size(); ++s)
		{
			if (sequence->at(s) == (*i)->sequence.at(s))
				found = KEYBIND_FIND_BUFFERED;
			else
				break;
		}

		if (s == (*i)->sequence.size())
		{
			*action = (*i)->action;
			*params = (*i)->params;
			return KEYBIND_FIND_EXACT;
		}
	}

	return found;
}

void Keybindings::truncate()
{
	vector<Keybinding *>::const_iterator i;

	for (i = bindings.begin(); i != bindings.end(); ++i)
		delete *i;

	bindings.clear();
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/pms

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/pms

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master