-* Copyright (C) 2005-09 by the Quassel Project *
-* devel@quassel-irc.org *
-* *
-* 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) version 3. *
-* *
-* 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., *
-* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
-***************************************************************************/
+ * Copyright (C) 2005-2019 by the Quassel Project *
+ * devel@quassel-irc.org *
+ * *
+ * 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) version 3. *
+ * *
+ * 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. *
+ ***************************************************************************/
BufferId TabCompleter::_currentBufferId;
QString TabCompleter::_currentBufferName;
TabCompleter::Type TabCompleter::_completionType;
BufferId TabCompleter::_currentBufferId;
QString TabCompleter::_currentBufferName;
TabCompleter::Type TabCompleter::_completionType;
-TabCompleter::TabCompleter(MultiLineEdit *_lineEdit)
- : QObject(_lineEdit),
- _lineEdit(_lineEdit),
- _enabled(false),
- _nickSuffix(": ")
+TabCompleter::TabCompleter(MultiLineEdit* _lineEdit)
+ : QObject(_lineEdit)
+ , _lineEdit(_lineEdit)
+ , _enabled(false)
+ , _nickSuffix(": ")
+{
+ // This Action just serves as a container for the custom shortcut and isn't actually handled;
+ // apparently, using tab as an Action shortcut in an input widget is unreliable on some platforms (e.g. OS/2)
+ _lineEdit->installEventFilter(this);
+ ActionCollection* coll = GraphicalUi::actionCollection("General");
+ QAction* a = coll->addAction("TabCompletionKey",
+ new Action(tr("Tab completion"), coll, this, &TabCompleter::onTabCompletionKey, QKeySequence(Qt::Key_Tab)));
+ a->setEnabled(false); // avoid catching the shortcut
+}
+
+void TabCompleter::onTabCompletionKey()
-void TabCompleter::buildCompletionList() {
- // ensure a safe state in case we return early.
- _completionMap.clear();
- _nextCompletion = _completionMap.begin();
-
- // this is the first time tab is pressed -> build up the completion list and it's iterator
- QModelIndex currentIndex = Client::bufferModel()->currentIndex();
- _currentBufferId = currentIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
- if(!_currentBufferId.isValid())
- return;
-
- NetworkId networkId = currentIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>();
- _currentBufferName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
-
- _currentNetwork = Client::network(networkId);
- if(!_currentNetwork)
- return;
-
- QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(QRegExp("[^#\\w\\d-_\\[\\]{}|`^.\\\\]"),-1,-1);
- QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
-
- // channel completion - add all channels of the current network to the map
- if(tabAbbrev.startsWith('#')) {
- _completionType = ChannelTab;
- foreach(IrcChannel *ircChannel, _currentNetwork->ircChannels()) {
- if(regex.indexIn(ircChannel->name()) > -1)
- _completionMap[ircChannel->name()] = ircChannel->name();
+void TabCompleter::buildCompletionList()
+{
+ // ensure a safe state in case we return early.
+ _completionMap.clear();
+ _nextCompletion = _completionMap.begin();
+
+ // this is the first time tab is pressed -> build up the completion list and it's iterator
+ QModelIndex currentIndex = Client::bufferModel()->currentIndex();
+ _currentBufferId = currentIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
+ if (!_currentBufferId.isValid())
+ return;
+
+ NetworkId networkId = currentIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>();
+ _currentBufferName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
+
+ _currentNetwork = Client::network(networkId);
+ if (!_currentNetwork)
+ return;
+
+ QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(QRegExp(R"([^#\w\d-_\[\]{}|`^.\\])"), -1, -1);
+ QRegExp regex(QString(R"(^[-_\[\]{}|`^.\\]*)").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
+
+ // channel completion - add all channels of the current network to the map
+ if (tabAbbrev.startsWith('#')) {
+ _completionType = ChannelTab;
+ foreach (IrcChannel* ircChannel, _currentNetwork->ircChannels()) {
+ if (regex.indexIn(ircChannel->name()) > -1)
+ _completionMap[ircChannel->name()] = ircChannel->name();
+ }
- } else {
- // user completion
- _completionType = UserTab;
- switch(static_cast<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
- case BufferInfo::ChannelBuffer:
- { // scope is needed for local var declaration
- IrcChannel *channel = _currentNetwork->ircChannel(_currentBufferName);
- if(!channel)
- return;
- foreach(IrcUser *ircUser, channel->ircUsers()) {
- if(regex.indexIn(ircUser->nick()) > -1)
- _completionMap[ircUser->nick().toLower()] = ircUser->nick();
+ else {
+ // user completion
+ _completionType = UserTab;
+ switch (static_cast<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
+ case BufferInfo::ChannelBuffer: { // scope is needed for local var declaration
+ IrcChannel* channel = _currentNetwork->ircChannel(_currentBufferName);
+ if (!channel)
+ return;
+ foreach (IrcUser* ircUser, channel->ircUsers()) {
+ if (regex.indexIn(ircUser->nick()) > -1)
+ _completionMap[ircUser->nick().toLower()] = ircUser->nick();
+ }
+ } break;
+ case BufferInfo::QueryBuffer:
+ if (regex.indexIn(_currentBufferName) > -1)
+ _completionMap[_currentBufferName.toLower()] = _currentBufferName;
+ // fallthrough
+ case BufferInfo::StatusBuffer:
+ if (!_currentNetwork->myNick().isEmpty() && regex.indexIn(_currentNetwork->myNick()) > -1)
+ _completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick();
+ break;
+ default:
+ return;
- }
- break;
- case BufferInfo::QueryBuffer:
- if(regex.indexIn(_currentBufferName) > -1)
- _completionMap[_currentBufferName.toLower()] = _currentBufferName;
- case BufferInfo::StatusBuffer:
- if(!_currentNetwork->myNick().isEmpty() && regex.indexIn(_currentNetwork->myNick()) > -1)
- _completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick();
- break;
- default:
- return;
- // we're completing the first word of the line
- if(_completionType == UserTab && _lineEdit->cursorPosition() == _lastCompletionLength) {
- _lineEdit->insert(_nickSuffix);
- _lastCompletionLength += _nickSuffix.length();
- }
+ // remember charcount to delete next time and advance to next completion
+ _lastCompletionLength = _nextCompletion->length();
+ _nextCompletion++;
+
+ // we're completing the first word of the line
+ if (_completionType == UserTab && _lineEdit->cursorPosition() == _lastCompletionLength) {
+ _lineEdit->insert(_nickSuffix);
+ _lastCompletionLength += _nickSuffix.length();
+ }
+ else if (s.addSpaceMidSentence()) {
+ _lineEdit->addCompletionSpace();
+ _lastCompletionLength++;
+ }
- // we're at the end of the list -> start over again
- } else {
- if(!_completionMap.isEmpty()) {
- _nextCompletion = _completionMap.begin();
- complete();
+ // we're at the end of the list -> start over again
+ }
+ else {
+ if (!_completionMap.isEmpty()) {
+ _nextCompletion = _completionMap.begin();
+ complete();
+ }
-bool TabCompleter::eventFilter(QObject *obj, QEvent *event) {
- if(obj != _lineEdit || event->type() != QEvent::KeyPress)
- return QObject::eventFilter(obj, event);
+bool TabCompleter::eventFilter(QObject* obj, QEvent* event)
+{
+ if (obj != _lineEdit || event->type() != QEvent::KeyPress)
+ return QObject::eventFilter(obj, event);
+
+ auto* keyEvent = static_cast<QKeyEvent*>(event);
-bool TabCompleter::CompletionKey::operator<(const CompletionKey &other) const {
- switch(_completionType) {
- case UserTab:
- {
- IrcUser *thisUser = _currentNetwork->ircUser(this->contents);
- if(thisUser && _currentNetwork->isMe(thisUser))
- return false;
+bool TabCompleter::CompletionKey::operator<(const CompletionKey& other) const
+{
+ switch (_completionType) {
+ case UserTab: {
+ IrcUser* thisUser = _currentNetwork->ircUser(this->contents);
+ if (thisUser && _currentNetwork->isMe(thisUser))
+ return false;