1/*
2 This source file is part of Konsole, a terminal emulator.
3
4 Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20*/
21
22// Own
23#include "KeyboardTranslator.h"
24
25// System
26#include <ctype.h>
27#include <stdio.h>
28
29// Qt
30#include <QBuffer>
31#include <QFile>
32#include <QFileInfo>
33#include <QTextStream>
34#include <QKeySequence>
35#include <QDir>
36#include <QtDebug>
37
38#include "tools.h"
39
40// KDE
41//#include <KDebug>
42//#include <KLocale>
43//#include <KStandardDirs>
44
45using namespace Konsole;
46
47
48const QByteArray KeyboardTranslatorManager::defaultTranslatorText(
49"keyboard \"Fallback Key Translator\"\n"
50"key Tab : \"\\t\""
51);
52
53KeyboardTranslatorManager::KeyboardTranslatorManager()
54 : _haveLoadedAll(false)
55{
56}
57KeyboardTranslatorManager::~KeyboardTranslatorManager()
58{
59 qDeleteAll(_translators);
60}
61QString KeyboardTranslatorManager::findTranslatorPath(const QString& name)
62{
63 return QString(get_kb_layout_dir() + name + QLatin1String(".keytab"));
64 //return KGlobal::dirs()->findResource("data","konsole/"+name+".keytab");
65}
66
67void KeyboardTranslatorManager::findTranslators()
68{
69 QDir dir(get_kb_layout_dir());
70 QStringList filters;
71 filters << QLatin1String("*.keytab");
72 dir.setNameFilters(filters);
73 QStringList list = dir.entryList(filters);
74// QStringList list = KGlobal::dirs()->findAllResources("data",
75// "konsole/*.keytab",
76// KStandardDirs::NoDuplicates);
77
78 // add the name of each translator to the list and associated
79 // the name with a null pointer to indicate that the translator
80 // has not yet been loaded from disk
81 QStringListIterator listIter(list);
82 while (listIter.hasNext())
83 {
84 QString translatorPath = listIter.next();
85
86 QString name = QFileInfo(translatorPath).baseName();
87
88 if ( !_translators.contains(name) )
89 _translators.insert(name,0);
90 }
91
92 _haveLoadedAll = true;
93}
94
95const KeyboardTranslator* KeyboardTranslatorManager::findTranslator(const QString& name)
96{
97 if ( name.isEmpty() )
98 return defaultTranslator();
99
100 if ( _translators.contains(name) && _translators[name] != 0 )
101 return _translators[name];
102
103 KeyboardTranslator* translator = loadTranslator(name);
104
105 if ( translator != 0 )
106 _translators[name] = translator;
107 else if ( !name.isEmpty() )
108 qDebug() << "Unable to load translator" << name;
109
110 return translator;
111}
112
113bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator* translator)
114{
115qDebug() << "KeyboardTranslatorManager::saveTranslator" << "unimplemented";
116Q_UNUSED(translator);
117#if 0
118 const QString path = KGlobal::dirs()->saveLocation("data","konsole/")+translator->name()
119 +".keytab";
120
121 //kDebug() << "Saving translator to" << path;
122
123 QFile destination(path);
124 if (!destination.open(QIODevice::WriteOnly | QIODevice::Text))
125 {
126 qDebug() << "Unable to save keyboard translation:"
127 << destination.errorString();
128 return false;
129 }
130
131 {
132 KeyboardTranslatorWriter writer(&destination);
133 writer.writeHeader(translator->description());
134
135 QListIterator<KeyboardTranslator::Entry> iter(translator->entries());
136 while ( iter.hasNext() )
137 writer.writeEntry(iter.next());
138 }
139
140 destination.close();
141#endif
142 return true;
143}
144
145KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(const QString& name)
146{
147 const QString& path = findTranslatorPath(name);
148
149 QFile source(path);
150 if (name.isEmpty() || !source.open(QIODevice::ReadOnly | QIODevice::Text))
151 return 0;
152
153 return loadTranslator(&source,name);
154}
155
156const KeyboardTranslator* KeyboardTranslatorManager::defaultTranslator()
157{
158 // Try to find the default.keytab file if it exists, otherwise
159 // fall back to the hard-coded one
160 const KeyboardTranslator* translator = findTranslator(QLatin1String("default"));
161 if (!translator)
162 {
163 QBuffer textBuffer;
164 textBuffer.setData(defaultTranslatorText);
165 textBuffer.open(QIODevice::ReadOnly);
166 translator = loadTranslator(&textBuffer,QLatin1String("fallback"));
167 }
168 return translator;
169}
170
171KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(QIODevice* source,const QString& name)
172{
173 KeyboardTranslator* translator = new KeyboardTranslator(name);
174 KeyboardTranslatorReader reader(source);
175 translator->setDescription( reader.description() );
176 while ( reader.hasNextEntry() )
177 translator->addEntry(reader.nextEntry());
178
179 source->close();
180
181 if ( !reader.parseError() )
182 {
183 return translator;
184 }
185 else
186 {
187 delete translator;
188 return 0;
189 }
190}
191
192KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice* destination)
193: _destination(destination)
194{
195 Q_ASSERT( destination && destination->isWritable() );
196
197 _writer = new QTextStream(_destination);
198}
199KeyboardTranslatorWriter::~KeyboardTranslatorWriter()
200{
201 delete _writer;
202}
203void KeyboardTranslatorWriter::writeHeader( const QString& description )
204{
205 *_writer << "keyboard \"" << description << '\"' << '\n';
206}
207void KeyboardTranslatorWriter::writeEntry( const KeyboardTranslator::Entry& entry )
208{
209 QString result;
210 if ( entry.command() != KeyboardTranslator::NoCommand )
211 result = entry.resultToString();
212 else
213 result = QLatin1Char('\"') + entry.resultToString() + QLatin1Char('\"');
214
215 *_writer << QLatin1String("key ") << entry.conditionToString() << QLatin1String(" : ") << result << QLatin1Char('\n');
216}
217
218
219// each line of the keyboard translation file is one of:
220//
221// - keyboard "name"
222// - key KeySequence : "characters"
223// - key KeySequence : CommandName
224//
225// KeySequence begins with the name of the key ( taken from the Qt::Key enum )
226// and is followed by the keyboard modifiers and state flags ( with + or - in front
227// of each modifier or flag to indicate whether it is required ). All keyboard modifiers
228// and flags are optional, if a particular modifier or state is not specified it is
229// assumed not to be a part of the sequence. The key sequence may contain whitespace
230//
231// eg: "key Up+Shift : scrollLineUp"
232// "key Next-Shift : "\E[6~"
233//
234// (lines containing only whitespace are ignored, parseLine assumes that comments have
235// already been removed)
236//
237
238KeyboardTranslatorReader::KeyboardTranslatorReader( QIODevice* source )
239 : _source(source)
240 , _hasNext(false)
241{
242 // read input until we find the description
243 while ( _description.isEmpty() && !source->atEnd() )
244 {
245 QList<Token> tokens = tokenize( QString::fromUtf8(source->readLine()) );
246 if ( !tokens.isEmpty() && tokens.first().type == Token::TitleKeyword )
247 _description = tokens[1].text;
248 }
249 // read first entry (if any)
250 readNext();
251}
252void KeyboardTranslatorReader::readNext()
253{
254 // find next entry
255 while ( !_source->atEnd() )
256 {
257 const QList<Token>& tokens = tokenize( QString::fromUtf8(_source->readLine()) );
258 if ( !tokens.isEmpty() && tokens.first().type == Token::KeyKeyword )
259 {
260 KeyboardTranslator::States flags = KeyboardTranslator::NoState;
261 KeyboardTranslator::States flagMask = KeyboardTranslator::NoState;
262 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
263 Qt::KeyboardModifiers modifierMask = Qt::NoModifier;
264
265 int keyCode = Qt::Key_unknown;
266
267 decodeSequence(tokens[1].text.toLower(),
268 keyCode,
269 modifiers,
270 modifierMask,
271 flags,
272 flagMask);
273
274 KeyboardTranslator::Command command = KeyboardTranslator::NoCommand;
275 QByteArray text;
276
277 // get text or command
278 if ( tokens[2].type == Token::OutputText )
279 {
280 text = tokens[2].text.toLocal8Bit();
281 }
282 else if ( tokens[2].type == Token::Command )
283 {
284 // identify command
285 if (!parseAsCommand(tokens[2].text,command))
286 qDebug() << "Command" << tokens[2].text << "not understood.";
287 }
288
289 KeyboardTranslator::Entry newEntry;
290 newEntry.setKeyCode( keyCode );
291 newEntry.setState( flags );
292 newEntry.setStateMask( flagMask );
293 newEntry.setModifiers( modifiers );
294 newEntry.setModifierMask( modifierMask );
295 newEntry.setText( text );
296 newEntry.setCommand( command );
297
298 _nextEntry = newEntry;
299
300 _hasNext = true;
301
302 return;
303 }
304 }
305
306 _hasNext = false;
307}
308
309bool KeyboardTranslatorReader::parseAsCommand(const QString& text,KeyboardTranslator::Command& command)
310{
311 if ( text.compare(QLatin1String("erase"),Qt::CaseInsensitive) == 0 )
312 command = KeyboardTranslator::EraseCommand;
313 else if ( text.compare(QLatin1String("scrollpageup"),Qt::CaseInsensitive) == 0 )
314 command = KeyboardTranslator::ScrollPageUpCommand;
315 else if ( text.compare(QLatin1String("scrollpagedown"),Qt::CaseInsensitive) == 0 )
316 command = KeyboardTranslator::ScrollPageDownCommand;
317 else if ( text.compare(QLatin1String("scrolllineup"),Qt::CaseInsensitive) == 0 )
318 command = KeyboardTranslator::ScrollLineUpCommand;
319 else if ( text.compare(QLatin1String("scrolllinedown"),Qt::CaseInsensitive) == 0 )
320 command = KeyboardTranslator::ScrollLineDownCommand;
321 else if ( text.compare(QLatin1String("scrolllock"),Qt::CaseInsensitive) == 0 )
322 command = KeyboardTranslator::ScrollLockCommand;
323 else if ( text.compare(QLatin1String("scrolluptotop"),Qt::CaseInsensitive) == 0)
324 command = KeyboardTranslator::ScrollUpToTopCommand;
325 else if ( text.compare(QLatin1String("scrolldowntobottom"),Qt::CaseInsensitive) == 0)
326 command = KeyboardTranslator::ScrollDownToBottomCommand;
327 else
328 return false;
329
330 return true;
331}
332
333bool KeyboardTranslatorReader::decodeSequence(const QString& text,
334 int& keyCode,
335 Qt::KeyboardModifiers& modifiers,
336 Qt::KeyboardModifiers& modifierMask,
337 KeyboardTranslator::States& flags,
338 KeyboardTranslator::States& flagMask)
339{
340 bool isWanted = true;
341 bool endOfItem = false;
342 QString buffer;
343
344 Qt::KeyboardModifiers tempModifiers = modifiers;
345 Qt::KeyboardModifiers tempModifierMask = modifierMask;
346 KeyboardTranslator::States tempFlags = flags;
347 KeyboardTranslator::States tempFlagMask = flagMask;
348
349 for ( int i = 0 ; i < text.count() ; i++ )
350 {
351 const QChar& ch = text[i];
352 bool isFirstLetter = i == 0;
353 bool isLastLetter = ( i == text.count()-1 );
354 endOfItem = true;
355 if ( ch.isLetterOrNumber() )
356 {
357 endOfItem = false;
358 buffer.append(ch);
359 } else if ( isFirstLetter )
360 {
361 buffer.append(ch);
362 }
363
364 if ( (endOfItem || isLastLetter) && !buffer.isEmpty() )
365 {
366 Qt::KeyboardModifier itemModifier = Qt::NoModifier;
367 int itemKeyCode = 0;
368 KeyboardTranslator::State itemFlag = KeyboardTranslator::NoState;
369
370 if ( parseAsModifier(buffer,itemModifier) )
371 {
372 tempModifierMask |= itemModifier;
373
374 if ( isWanted )
375 tempModifiers |= itemModifier;
376 }
377 else if ( parseAsStateFlag(buffer,itemFlag) )
378 {
379 tempFlagMask |= itemFlag;
380
381 if ( isWanted )
382 tempFlags |= itemFlag;
383 }
384 else if ( parseAsKeyCode(buffer,itemKeyCode) )
385 keyCode = itemKeyCode;
386 else
387 qDebug() << "Unable to parse key binding item:" << buffer;
388
389 buffer.clear();
390 }
391
392 // check if this is a wanted / not-wanted flag and update the
393 // state ready for the next item
394 if ( ch == QLatin1Char('+') )
395 isWanted = true;
396 else if ( ch == QLatin1Char('-') )
397 isWanted = false;
398 }
399
400 modifiers = tempModifiers;
401 modifierMask = tempModifierMask;
402 flags = tempFlags;
403 flagMask = tempFlagMask;
404
405 return true;
406}
407
408bool KeyboardTranslatorReader::parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier)
409{
410 if ( item == QLatin1String("shift") )
411 modifier = Qt::ShiftModifier;
412 else if ( item == QLatin1String("ctrl") || item == QLatin1String("control") )
413 modifier = Qt::ControlModifier;
414 else if ( item == QLatin1String("alt") )
415 modifier = Qt::AltModifier;
416 else if ( item == QLatin1String("meta") )
417 modifier = Qt::MetaModifier;
418 else if ( item == QLatin1String("keypad") )
419 modifier = Qt::KeypadModifier;
420 else
421 return false;
422
423 return true;
424}
425bool KeyboardTranslatorReader::parseAsStateFlag(const QString& item , KeyboardTranslator::State& flag)
426{
427 if ( item == QLatin1String("appcukeys") || item == QLatin1String("appcursorkeys") )
428 flag = KeyboardTranslator::CursorKeysState;
429 else if ( item == QLatin1String("ansi") )
430 flag = KeyboardTranslator::AnsiState;
431 else if ( item == QLatin1String("newline") )
432 flag = KeyboardTranslator::NewLineState;
433 else if ( item == QLatin1String("appscreen") )
434 flag = KeyboardTranslator::AlternateScreenState;
435 else if ( item == QLatin1String("anymod") || item == QLatin1String("anymodifier") )
436 flag = KeyboardTranslator::AnyModifierState;
437 else if ( item == QLatin1String("appkeypad") )
438 flag = KeyboardTranslator::ApplicationKeypadState;
439 else
440 return false;
441
442 return true;
443}
444bool KeyboardTranslatorReader::parseAsKeyCode(const QString& item , int& keyCode)
445{
446 QKeySequence sequence = QKeySequence::fromString(item);
447 if ( !sequence.isEmpty() )
448 {
449 keyCode = sequence[0];
450
451 if ( sequence.count() > 1 )
452 {
453 qDebug() << "Unhandled key codes in sequence: " << item;
454 }
455 }
456 // additional cases implemented for backwards compatibility with KDE 3
457 else if ( item == QLatin1String("prior") )
458 keyCode = Qt::Key_PageUp;
459 else if ( item == QLatin1String("next") )
460 keyCode = Qt::Key_PageDown;
461 else
462 return false;
463
464 return true;
465}
466
467QString KeyboardTranslatorReader::description() const
468{
469 return _description;
470}
471bool KeyboardTranslatorReader::hasNextEntry()
472{
473 return _hasNext;
474}
475KeyboardTranslator::Entry KeyboardTranslatorReader::createEntry( const QString& condition ,
476 const QString& result )
477{
478 QString entryString = QString::fromLatin1("keyboard \"temporary\"\nkey ");
479 entryString.append(condition);
480 entryString.append(QLatin1String(" : "));
481
482 // if 'result' is the name of a command then the entry result will be that command,
483 // otherwise the result will be treated as a string to echo when the key sequence
484 // specified by 'condition' is pressed
485 KeyboardTranslator::Command command;
486 if (parseAsCommand(result,command))
487 entryString.append(result);
488 else
489 entryString.append(QLatin1Char('\"') + result + QLatin1Char('\"'));
490
491 QByteArray array = entryString.toUtf8();
492 QBuffer buffer(&array);
493 buffer.open(QIODevice::ReadOnly);
494 KeyboardTranslatorReader reader(&buffer);
495
496 KeyboardTranslator::Entry entry;
497 if ( reader.hasNextEntry() )
498 entry = reader.nextEntry();
499
500 return entry;
501}
502
503KeyboardTranslator::Entry KeyboardTranslatorReader::nextEntry()
504{
505 Q_ASSERT( _hasNext );
506 KeyboardTranslator::Entry entry = _nextEntry;
507 readNext();
508 return entry;
509}
510bool KeyboardTranslatorReader::parseError()
511{
512 return false;
513}
514QList<KeyboardTranslatorReader::Token> KeyboardTranslatorReader::tokenize(const QString& line)
515{
516 QString text = line;
517
518 // remove comments
519 bool inQuotes = false;
520 int commentPos = -1;
521 for (int i=text.length()-1;i>=0;i--)
522 {
523 QChar ch = text[i];
524 if (ch == QLatin1Char('\"'))
525 inQuotes = !inQuotes;
526 else if (ch == QLatin1Char('#') && !inQuotes)
527 commentPos = i;
528 }
529 if (commentPos != -1)
530 text.remove(commentPos,text.length());
531
532 text = text.simplified();
533
534 // title line: keyboard "title"
535 static QRegExp title(QLatin1String("keyboard\\s+\"(.*)\""));
536 // key line: key KeySequence : "output"
537 // key line: key KeySequence : command
538 static QRegExp key(QLatin1String("key\\s+([\\w\\+\\s\\-\\*\\.]+)\\s*:\\s*(\"(.*)\"|\\w+)"));
539
540 QList<Token> list;
541 if ( text.isEmpty() )
542 {
543 return list;
544 }
545
546 if ( title.exactMatch(text) )
547 {
548 Token titleToken = { Token::TitleKeyword , QString() };
549 Token textToken = { Token::TitleText , title.capturedTexts().at(1) };
550
551 list << titleToken << textToken;
552 }
553 else if ( key.exactMatch(text) )
554 {
555 Token keyToken = { Token::KeyKeyword , QString() };
556 Token sequenceToken = { Token::KeySequence , key.capturedTexts().value(1).remove(QLatin1Char(' ')) };
557
558 list << keyToken << sequenceToken;
559
560 if ( key.capturedTexts().at(3).isEmpty() )
561 {
562 // capturedTexts()[2] is a command
563 Token commandToken = { Token::Command , key.capturedTexts().at(2) };
564 list << commandToken;
565 }
566 else
567 {
568 // capturedTexts()[3] is the output string
569 Token outputToken = { Token::OutputText , key.capturedTexts().at(3) };
570 list << outputToken;
571 }
572 }
573 else
574 {
575 qDebug() << "Line in keyboard translator file could not be understood:" << text;
576 }
577
578 return list;
579}
580
581QList<QString> KeyboardTranslatorManager::allTranslators()
582{
583 if ( !_haveLoadedAll )
584 {
585 findTranslators();
586 }
587
588 return _translators.keys();
589}
590
591KeyboardTranslator::Entry::Entry()
592: _keyCode(0)
593, _modifiers(Qt::NoModifier)
594, _modifierMask(Qt::NoModifier)
595, _state(NoState)
596, _stateMask(NoState)
597, _command(NoCommand)
598{
599}
600
601bool KeyboardTranslator::Entry::operator==(const Entry& rhs) const
602{
603 return _keyCode == rhs._keyCode &&
604 _modifiers == rhs._modifiers &&
605 _modifierMask == rhs._modifierMask &&
606 _state == rhs._state &&
607 _stateMask == rhs._stateMask &&
608 _command == rhs._command &&
609 _text == rhs._text;
610}
611
612bool KeyboardTranslator::Entry::matches(int keyCode ,
613 Qt::KeyboardModifiers modifiers,
614 States testState) const
615{
616 if ( _keyCode != keyCode )
617 return false;
618
619 if ( (modifiers & _modifierMask) != (_modifiers & _modifierMask) )
620 return false;
621
622 // if modifiers is non-zero, the 'any modifier' state is implicit
623 if ( modifiers != 0 )
624 testState |= AnyModifierState;
625
626 if ( (testState & _stateMask) != (_state & _stateMask) )
627 return false;
628
629 // special handling for the 'Any Modifier' state, which checks for the presence of
630 // any or no modifiers. In this context, the 'keypad' modifier does not count.
631 bool anyModifiersSet = modifiers != 0 && modifiers != Qt::KeypadModifier;
632 bool wantAnyModifier = _state & KeyboardTranslator::AnyModifierState;
633 if ( _stateMask & KeyboardTranslator::AnyModifierState )
634 {
635 if ( wantAnyModifier != anyModifiersSet )
636 return false;
637 }
638
639 return true;
640}
641QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards,Qt::KeyboardModifiers modifiers) const
642{
643 QByteArray result(text(expandWildCards,modifiers));
644
645 for ( int i = 0 ; i < result.count() ; i++ )
646 {
647 char ch = result[i];
648 char replacement = 0;
649
650 switch ( ch )
651 {
652 case 27 : replacement = 'E'; break;
653 case 8 : replacement = 'b'; break;
654 case 12 : replacement = 'f'; break;
655 case 9 : replacement = 't'; break;
656 case 13 : replacement = 'r'; break;
657 case 10 : replacement = 'n'; break;
658 default:
659 // any character which is not printable is replaced by an equivalent
660 // \xhh escape sequence (where 'hh' are the corresponding hex digits)
661 if ( !QChar(QLatin1Char(ch)).isPrint() )
662 replacement = 'x';
663 }
664
665 if ( replacement == 'x' )
666 {
667 result.replace(i,1,"\\x"+QByteArray(1,ch).toHex());
668 } else if ( replacement != 0 )
669 {
670 result.remove(i,1);
671 result.insert(i,'\\');
672 result.insert(i+1,replacement);
673 }
674 }
675
676 return result;
677}
678QByteArray KeyboardTranslator::Entry::unescape(const QByteArray& input) const
679{
680 QByteArray result(input);
681
682 for ( int i = 0 ; i < result.count()-1 ; i++ )
683 {
684
685 QByteRef ch = result[i];
686 if ( ch == '\\' )
687 {
688 char replacement[2] = {0,0};
689 int charsToRemove = 2;
690 bool escapedChar = true;
691
692 switch ( result[i+1] )
693 {
694 case 'E' : replacement[0] = 27; break;
695 case 'b' : replacement[0] = 8 ; break;
696 case 'f' : replacement[0] = 12; break;
697 case 't' : replacement[0] = 9 ; break;
698 case 'r' : replacement[0] = 13; break;
699 case 'n' : replacement[0] = 10; break;
700 case 'x' :
701 {
702 // format is \xh or \xhh where 'h' is a hexadecimal
703 // digit from 0-9 or A-F which should be replaced
704 // with the corresponding character value
705 char hexDigits[3] = {0};
706
707 if ( (i < result.count()-2) && isxdigit(result[i+2]) )
708 hexDigits[0] = result[i+2];
709 if ( (i < result.count()-3) && isxdigit(result[i+3]) )
710 hexDigits[1] = result[i+3];
711
712 unsigned charValue = 0;
713 sscanf(hexDigits,"%x",&charValue);
714
715 replacement[0] = (char)charValue;
716 charsToRemove = 2 + strlen(hexDigits);
717 }
718 break;
719 default:
720 escapedChar = false;
721 }
722
723 if ( escapedChar )
724 result.replace(i,charsToRemove,replacement);
725 }
726 }
727
728 return result;
729}
730
731void KeyboardTranslator::Entry::insertModifier( QString& item , int modifier ) const
732{
733 if ( !(modifier & _modifierMask) )
734 return;
735
736 if ( modifier & _modifiers )
737 item += QLatin1Char('+');
738 else
739 item += QLatin1Char('-');
740
741 if ( modifier == Qt::ShiftModifier )
742 item += QLatin1String("Shift");
743 else if ( modifier == Qt::ControlModifier )
744 item += QLatin1String("Ctrl");
745 else if ( modifier == Qt::AltModifier )
746 item += QLatin1String("Alt");
747 else if ( modifier == Qt::MetaModifier )
748 item += QLatin1String("Meta");
749 else if ( modifier == Qt::KeypadModifier )
750 item += QLatin1String("KeyPad");
751}
752void KeyboardTranslator::Entry::insertState( QString& item , int state ) const
753{
754 if ( !(state & _stateMask) )
755 return;
756
757 if ( state & _state )
758 item += QLatin1Char('+') ;
759 else
760 item += QLatin1Char('-') ;
761
762 if ( state == KeyboardTranslator::AlternateScreenState )
763 item += QLatin1String("AppScreen");
764 else if ( state == KeyboardTranslator::NewLineState )
765 item += QLatin1String("NewLine");
766 else if ( state == KeyboardTranslator::AnsiState )
767 item += QLatin1String("Ansi");
768 else if ( state == KeyboardTranslator::CursorKeysState )
769 item += QLatin1String("AppCursorKeys");
770 else if ( state == KeyboardTranslator::AnyModifierState )
771 item += QLatin1String("AnyModifier");
772 else if ( state == KeyboardTranslator::ApplicationKeypadState )
773 item += QLatin1String("AppKeypad");
774}
775QString KeyboardTranslator::Entry::resultToString(bool expandWildCards,Qt::KeyboardModifiers modifiers) const
776{
777 if ( !_text.isEmpty() )
778 return QString::fromLatin1(escapedText(expandWildCards,modifiers));
779 else if ( _command == EraseCommand )
780 return QLatin1String("Erase");
781 else if ( _command == ScrollPageUpCommand )
782 return QLatin1String("ScrollPageUp");
783 else if ( _command == ScrollPageDownCommand )
784 return QLatin1String("ScrollPageDown");
785 else if ( _command == ScrollLineUpCommand )
786 return QLatin1String("ScrollLineUp");
787 else if ( _command == ScrollLineDownCommand )
788 return QLatin1String("ScrollLineDown");
789 else if ( _command == ScrollLockCommand )
790 return QLatin1String("ScrollLock");
791 else if (_command == ScrollUpToTopCommand)
792 return QLatin1String("ScrollUpToTop");
793 else if (_command == ScrollDownToBottomCommand)
794 return QLatin1String("ScrollDownToBottom");
795
796 return QString();
797}
798QString KeyboardTranslator::Entry::conditionToString() const
799{
800 QString result = QKeySequence(_keyCode).toString();
801
802 insertModifier( result , Qt::ShiftModifier );
803 insertModifier( result , Qt::ControlModifier );
804 insertModifier( result , Qt::AltModifier );
805 insertModifier( result , Qt::MetaModifier );
806 insertModifier( result , Qt::KeypadModifier );
807
808 insertState( result , KeyboardTranslator::AlternateScreenState );
809 insertState( result , KeyboardTranslator::NewLineState );
810 insertState( result , KeyboardTranslator::AnsiState );
811 insertState( result , KeyboardTranslator::CursorKeysState );
812 insertState( result , KeyboardTranslator::AnyModifierState );
813 insertState( result , KeyboardTranslator::ApplicationKeypadState );
814
815 return result;
816}
817
818KeyboardTranslator::KeyboardTranslator(const QString& name)
819: _name(name)
820{
821}
822
823void KeyboardTranslator::setDescription(const QString& description)
824{
825 _description = description;
826}
827QString KeyboardTranslator::description() const
828{
829 return _description;
830}
831void KeyboardTranslator::setName(const QString& name)
832{
833 _name = name;
834}
835QString KeyboardTranslator::name() const
836{
837 return _name;
838}
839
840QList<KeyboardTranslator::Entry> KeyboardTranslator::entries() const
841{
842 return _entries.values();
843}
844
845void KeyboardTranslator::addEntry(const Entry& entry)
846{
847 const int keyCode = entry.keyCode();
848 _entries.insert(keyCode,entry);
849}
850void KeyboardTranslator::replaceEntry(const Entry& existing , const Entry& replacement)
851{
852 if ( !existing.isNull() )
853 _entries.remove(existing.keyCode(),existing);
854 _entries.insert(replacement.keyCode(),replacement);
855}
856void KeyboardTranslator::removeEntry(const Entry& entry)
857{
858 _entries.remove(entry.keyCode(),entry);
859}
860KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const
861{
862 for (auto it = _entries.cbegin(), end = _entries.cend(); it != end; ++it)
863 {
864 if (it.key() == keyCode)
865 if ( it.value().matches(keyCode,modifiers,state) )
866 return *it;
867 }
868 return Entry(); // entry not found
869}
870void KeyboardTranslatorManager::addTranslator(KeyboardTranslator* translator)
871{
872 _translators.insert(translator->name(),translator);
873
874 if ( !saveTranslator(translator) )
875 qDebug() << "Unable to save translator" << translator->name()
876 << "to disk.";
877}
878bool KeyboardTranslatorManager::deleteTranslator(const QString& name)
879{
880 Q_ASSERT( _translators.contains(name) );
881
882 // locate and delete
883 QString path = findTranslatorPath(name);
884 if ( QFile::remove(path) )
885 {
886 _translators.remove(name);
887 return true;
888 }
889 else
890 {
891 qDebug() << "Failed to remove translator - " << path;
892 return false;
893 }
894}
895Q_GLOBAL_STATIC( KeyboardTranslatorManager , theKeyboardTranslatorManager )
896KeyboardTranslatorManager* KeyboardTranslatorManager::instance()
897{
898 return theKeyboardTranslatorManager;
899}
900