You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

540 lines
17 KiB

/*
This source file is part of Konsole, a terminal emulator.
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
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 2 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#ifndef KEYBOARDTRANSLATOR_H
#define KEYBOARDTRANSLATOR_H
// Qt
#include <QHash>
#include <QList>
#include <QString>
#include <QMetaType>
// Konsole
#include "konsoleprivate_export.h"
class QIODevice;
class QTextStream;
namespace Konsole {
/**
* A converter which maps between key sequences pressed by the user and the
* character strings which should be sent to the terminal and commands
* which should be invoked when those character sequences are pressed.
*
* Konsole supports multiple keyboard translators, allowing the user to
* specify the character sequences which are sent to the terminal
* when particular key sequences are pressed.
*
* A key sequence is defined as a key code, associated keyboard modifiers
* (Shift,Ctrl,Alt,Meta etc.) and state flags which indicate the state
* which the terminal must be in for the key sequence to apply.
*/
class KONSOLEPRIVATE_EXPORT KeyboardTranslator
{
public:
/**
* The meaning of a particular key sequence may depend upon the state which
* the terminal emulation is in. Therefore findEntry() may return a different
* Entry depending upon the state flags supplied.
*
* This enum describes the states which may be associated with a particular
* entry in the keyboard translation entry.
*/
enum State {
/** Indicates that no special state is active */
NoState = 0,
/**
* TODO More documentation
*/
NewLineState = 1,
/**
* Indicates that the terminal is in 'ANSI' mode.
* TODO: More documentation
*/
AnsiState = 2,
/**
* TODO More documentation
*/
CursorKeysState = 4,
/**
* Indicates that the alternate screen ( typically used by interactive programs
* such as screen or vim ) is active
*/
AlternateScreenState = 8,
/** Indicates that any of the modifier keys is active. */
AnyModifierState = 16,
/** Indicates that the numpad is in application mode. */
ApplicationKeypadState = 32
};
Q_DECLARE_FLAGS(States, State)
/**
* This enum describes commands which are associated with particular key sequences.
*/
enum Command {
/** Indicates that no command is associated with this command sequence */
NoCommand = 0,
/** TODO Document me */
SendCommand = 1,
/** Scroll the terminal display up one page */
ScrollPageUpCommand = 2,
/** Scroll the terminal display down one page */
ScrollPageDownCommand = 4,
/** Scroll the terminal display up one line */
ScrollLineUpCommand = 8,
/** Scroll the terminal display down one line */
ScrollLineDownCommand = 16,
/** Scroll the terminal display up to the start of history */
ScrollUpToTopCommand = 32,
/** Scroll the terminal display down to the end of history */
ScrollDownToBottomCommand = 64,
/** Echos the operating system specific erase character. */
EraseCommand = 256
};
Q_DECLARE_FLAGS(Commands, Command)
/**
* Represents an association between a key sequence pressed by the user
* and the character sequence and commands associated with it for a particular
* KeyboardTranslator.
*/
class Entry
{
public:
/**
* Constructs a new entry for a keyboard translator.
*/
Entry();
/**
* Returns true if this entry is null.
* This is true for newly constructed entries which have no properties set.
*/
bool isNull() const;
/** Returns the commands associated with this entry */
Command command() const;
/** Sets the command associated with this entry. */
void setCommand(Command aCommand);
/**
* Returns the character sequence associated with this entry, optionally replacing
* wildcard '*' characters with numbers to indicate the keyboard modifiers being pressed.
*
* TODO: The numbers used to replace '*' characters are taken from the Konsole/KDE 3 code.
* Document them.
*
* @param expandWildCards Specifies whether wild cards (occurrences of the '*' character) in
* the entry should be replaced with a number to indicate the modifier keys being pressed.
*
* @param keyboardModifiers The keyboard modifiers being pressed.
*/
QByteArray text(bool expandWildCards = false,
Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier) const;
/** Sets the character sequence associated with this entry */
void setText(const QByteArray &aText);
/**
* Returns the character sequence associated with this entry,
* with any non-printable characters replaced with escape sequences.
*
* eg. \\E for Escape, \\t for tab, \\n for new line.
*
* @param expandWildCards See text()
* @param modifiers See text()
*/
QByteArray escapedText(bool expandWildCards = false,
Qt::KeyboardModifiers modifiers = Qt::NoModifier) const;
/** Returns the character code ( from the Qt::Key enum ) associated with this entry */
int keyCode() const;
/** Sets the character code associated with this entry */
void setKeyCode(int aKeyCode);
/**
* Returns a bitwise-OR of the enabled keyboard modifiers associated with this entry.
* If a modifier is set in modifierMask() but not in modifiers(), this means that the entry
* only matches when that modifier is NOT pressed.
*
* If a modifier is not set in modifierMask() then the entry matches whether the modifier
* is pressed or not.
*/
Qt::KeyboardModifiers modifiers() const;
/** Returns the keyboard modifiers which are valid in this entry. See modifiers() */
Qt::KeyboardModifiers modifierMask() const;
/** See modifiers() */
void setModifiers(Qt::KeyboardModifiers modifiers);
/** See modifierMask() and modifiers() */
void setModifierMask(Qt::KeyboardModifiers modifiers);
/**
* Returns a bitwise-OR of the enabled state flags associated with this entry.
* If flag is set in stateMask() but not in state(), this means that the entry only
* matches when the terminal is NOT in that state.
*
* If a state is not set in stateMask() then the entry matches whether the terminal
* is in that state or not.
*/
States state() const;
/** Returns the state flags which are valid in this entry. See state() */
States stateMask() const;
/** See state() */
void setState(States aState);
/** See stateMask() */
void setStateMask(States aStateMask);
/**
* Returns the key code and modifiers associated with this entry
* as a QKeySequence
*/
//QKeySequence keySequence() const;
/**
* Returns this entry's conditions ( ie. its key code, modifier and state criteria )
* as a string.
*/
QString conditionToString() const;
/**
* Returns this entry's result ( ie. its command or character sequence )
* as a string.
*
* @param expandWildCards See text()
* @param modifiers See text()
*/
QString resultToString(bool expandWildCards = false,
Qt::KeyboardModifiers modifiers = Qt::NoModifier) const;
/**
* Returns true if this entry matches the given key sequence, specified
* as a combination of @p keyCode , @p modifiers and @p state.
*/
bool matches(int keyCode, Qt::KeyboardModifiers modifiers, States flags) const;
bool operator==(const Entry &rhs) const;
private:
void insertModifier(QString &item, int modifier) const;
void insertState(QString &item, int state) const;
QByteArray unescape(const QByteArray &text) const;
int _keyCode;
Qt::KeyboardModifiers _modifiers;
Qt::KeyboardModifiers _modifierMask;
States _state;
States _stateMask;
Command _command;
QByteArray _text;
};
/** Constructs a new keyboard translator with the given @p name */
explicit KeyboardTranslator(const QString &name);
//KeyboardTranslator(const KeyboardTranslator& other);
/** Returns the name of this keyboard translator */
QString name() const;
/** Sets the name of this keyboard translator */
void setName(const QString &name);
/** Returns the descriptive name of this keyboard translator */
QString description() const;
/** Sets the descriptive name of this keyboard translator */
void setDescription(const QString &description);
/**
* Looks for an entry in this keyboard translator which matches the given
* key code, keyboard modifiers and state flags.
*
* Returns the matching entry if found or a null Entry otherwise ( ie.
* entry.isNull() will return true )
*
* @param keyCode A key code from the Qt::Key enum
* @param modifiers A combination of modifiers
* @param state Optional flags which specify the current state of the terminal
*/
Entry findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state = NoState) const;
/**
* Adds an entry to this keyboard translator's table. Entries can be looked up according
* to their key sequence using findEntry()
*/
void addEntry(const Entry &entry);
/**
* Replaces an entry in the translator. If the @p existing entry is null,
* then this is equivalent to calling addEntry(@p replacement)
*/
void replaceEntry(const Entry &existing, const Entry &replacement);
/**
* Removes an entry from the table.
*/
void removeEntry(const Entry &entry);
/** Returns a list of all entries in the translator. */
QList<Entry> entries() const;
private:
// All entries in this translator, indexed by their keycode
QMultiHash<int, Entry> _entries;
QString _name;
QString _description;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::States)
Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::Commands)
class FallbackKeyboardTranslator : public KeyboardTranslator
{
public:
FallbackKeyboardTranslator();
};
/**
* Parses the contents of a Keyboard Translator (.keytab) file and
* returns the entries found in it.
*
* Usage example:
*
* @code
* QFile source( "/path/to/keytab" );
* source.open( QIODevice::ReadOnly );
*
* KeyboardTranslator* translator = new KeyboardTranslator( "name-of-translator" );
*
* KeyboardTranslatorReader reader(source);
* while ( reader.hasNextEntry() )
* translator->addEntry(reader.nextEntry());
*
* source.close();
*
* if ( !reader.parseError() )
* {
* // parsing succeeded, do something with the translator
* }
* else
* {
* // parsing failed
* }
* @endcode
*/
class KeyboardTranslatorReader
{
public:
/** Constructs a new reader which parses the given @p source */
explicit KeyboardTranslatorReader(QIODevice *source);
~KeyboardTranslatorReader();
KeyboardTranslatorReader(const KeyboardTranslatorReader &) = delete;
KeyboardTranslatorReader &operator=(const KeyboardTranslatorReader &) = delete;
/**
* Returns the description text.
* TODO: More documentation
*/
QString description() const;
/** Returns true if there is another entry in the source stream */
bool hasNextEntry();
/** Returns the next entry found in the source stream */
KeyboardTranslator::Entry nextEntry();
/**
* Returns true if an error occurred whilst parsing the input or
* false if no error occurred.
*/
bool parseError();
/**
* Parses a condition and result string for a translator entry
* and produces a keyboard translator entry.
*
* The condition and result strings are in the same format as in
*/
static KeyboardTranslator::Entry createEntry(const QString &condition, const QString &result);
private:
struct Token {
enum Type {
TitleKeyword,
TitleText,
KeyKeyword,
KeySequence,
Command,
OutputText
};
Type type;
QString text;
};
QList<Token> tokenize(const QString &);
void readNext();
bool decodeSequence(const QString &, int &keyCode, Qt::KeyboardModifiers &modifiers,
Qt::KeyboardModifiers &modifierMask, KeyboardTranslator::States &state,
KeyboardTranslator::States &stateFlags);
static bool parseAsModifier(const QString &item, Qt::KeyboardModifier &modifier);
static bool parseAsStateFlag(const QString &item, KeyboardTranslator::State &state);
static bool parseAsKeyCode(const QString &item, int &keyCode);
static bool parseAsCommand(const QString &text, KeyboardTranslator::Command &command);
QIODevice *_source;
QString _description;
KeyboardTranslator::Entry _nextEntry;
bool _hasNext;
};
/** Writes a keyboard translation to disk. */
class KeyboardTranslatorWriter
{
public:
/**
* Constructs a new writer which saves data into @p destination.
* The caller is responsible for closing the device when writing is complete.
*/
explicit KeyboardTranslatorWriter(QIODevice *destination);
~KeyboardTranslatorWriter();
KeyboardTranslatorWriter(const KeyboardTranslatorWriter &) = delete;
KeyboardTranslatorWriter &operator=(const KeyboardTranslatorWriter &) = delete;
/**
* Writes the header for the keyboard translator.
* @param description Description of the keyboard translator.
*/
void writeHeader(const QString &description);
/** Writes a translator entry. */
void writeEntry(const KeyboardTranslator::Entry &entry);
private:
QIODevice *_destination;
QTextStream *_writer;
};
inline int KeyboardTranslator::Entry::keyCode() const
{
return _keyCode;
}
inline void KeyboardTranslator::Entry::setKeyCode(int aKeyCode)
{
_keyCode = aKeyCode;
}
inline void KeyboardTranslator::Entry::setModifiers(Qt::KeyboardModifiers modifier)
{
_modifiers = modifier;
}
inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifiers() const
{
return _modifiers;
}
inline void KeyboardTranslator::Entry::setModifierMask(Qt::KeyboardModifiers mask)
{
_modifierMask = mask;
}
inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifierMask() const
{
return _modifierMask;
}
inline bool KeyboardTranslator::Entry::isNull() const
{
return *this == Entry();
}
inline void KeyboardTranslator::Entry::setCommand(Command aCommand)
{
_command = aCommand;
}
inline KeyboardTranslator::Command KeyboardTranslator::Entry::command() const
{
return _command;
}
inline void KeyboardTranslator::Entry::setText(const QByteArray &aText)
{
_text = unescape(aText);
}
inline int oneOrZero(int value)
{
return value ? 1 : 0;
}
inline QByteArray KeyboardTranslator::Entry::text(bool expandWildCards,
Qt::KeyboardModifiers keyboardModifiers) const
{
QByteArray expandedText = _text;
if (expandWildCards) {
int modifierValue = 1;
modifierValue += oneOrZero(keyboardModifiers & Qt::ShiftModifier);
modifierValue += oneOrZero(keyboardModifiers & Qt::AltModifier) << 1;
modifierValue += oneOrZero(keyboardModifiers & Qt::ControlModifier) << 2;
for (int i = 0; i < _text.length(); i++) {
if (expandedText[i] == '*') {
expandedText[i] = '0' + modifierValue;
}
}
}
return expandedText;
}
inline void KeyboardTranslator::Entry::setState(States aState)
{
_state = aState;
}
inline KeyboardTranslator::States KeyboardTranslator::Entry::state() const
{
return _state;
}
inline void KeyboardTranslator::Entry::setStateMask(States aStateMask)
{
_stateMask = aStateMask;
}
inline KeyboardTranslator::States KeyboardTranslator::Entry::stateMask() const
{
return _stateMask;
}
}
Q_DECLARE_METATYPE(Konsole::KeyboardTranslator::Entry)
Q_DECLARE_METATYPE(const Konsole::KeyboardTranslator *)
#endif // KEYBOARDTRANSLATOR_H