/***************************************************************************
- * Copyright (C) 2005/06 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-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. *
+***************************************************************************/
#include "tabcompleter.h"
-#include "inputline.h"
-#include "client.h"
#include "buffermodel.h"
-#include "networkmodel.h"
-#include "network.h"
+#include "client.h"
#include "ircchannel.h"
#include "ircuser.h"
+#include "multilineedit.h"
+#include "network.h"
+#include "networkmodel.h"
#include "uisettings.h"
+#include "action.h"
+#include "actioncollection.h"
+#include "graphicalui.h"
#include <QRegExp>
-TabCompleter::TabCompleter(InputLine *inputLine_)
- : QObject(inputLine_),
- inputLine(inputLine_),
- enabled(false),
- nickSuffix(": ")
+const Network *TabCompleter::_currentNetwork;
+BufferId TabCompleter::_currentBufferId;
+QString TabCompleter::_currentBufferName;
+TabCompleter::Type TabCompleter::_completionType;
+
+TabCompleter::TabCompleter(MultiLineEdit *_lineEdit)
+ : QObject(_lineEdit),
+ _lineEdit(_lineEdit),
+ _enabled(false),
+ _nickSuffix(": ")
{
- inputLine->installEventFilter(this);
+ // use both an Action and generic eventFilter, to make the shortcut configurable
+ // yet still be able to reset() when required
+ _lineEdit->installEventFilter(this);
+ ActionCollection *coll = GraphicalUi::actionCollection("General");
+ coll->addAction("TabCompletionKey", new Action(tr("Tab completion"), coll,
+ this, SLOT(onTabCompletionKey()), QKeySequence(Qt::Key_Tab)));
+}
+
+void TabCompleter::onTabCompletionKey() {
+ complete();
}
void TabCompleter::buildCompletionList() {
- completionList.clear();
- nextCompletion = completionList.begin();
+ // 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();
- if(!currentIndex.data(NetworkModel::BufferIdRole).isValid())
+ _currentBufferId = currentIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
+ if(!_currentBufferId.isValid())
return;
-
+
NetworkId networkId = currentIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>();
- QString channelName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
+ _currentBufferName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
- const Network *network = Client::network(networkId);
- if(!network)
+ _currentNetwork = Client::network(networkId);
+ if(!_currentNetwork)
return;
- IrcChannel *channel = network->ircChannel(channelName);
- if(!channel)
- return;
+ QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(QRegExp("[^#\\w\\d-_\\[\\]{}|`^.\\\\]"),-1,-1);
+ QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
- // FIXME commented for debugging
- /*
- disconnect(this, SLOT(ircUserJoinedOrParted(IrcUser *)));
- connect(channel, SIGNAL(ircUserJoined(IrcUser *)),
- this, SLOT(ircUserJoinedOrParted(IrcUser *)));
- connect(channel, SIGNAL(ircUserParted(IrcUser *)),
- this, SLOT(ircUserJoinedOrParted(IrcUser *)));
- */
-
- completionList.clear();
- QString tabAbbrev = inputLine->text().left(inputLine->cursorPosition()).section(' ',-1,-1);
- completionList.clear();
- QRegExp regex(QString("^[^a-zA-Z]*").append(tabAbbrev), Qt::CaseInsensitive);
- QMap<QString, QString> sortMap;
-
- foreach(IrcUser *ircUser, channel->ircUsers()) {
- if(regex.indexIn(ircUser->nick()) > -1) {
- sortMap[ircUser->nick().toLower()] = ircUser->nick();
+ // 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();
+ }
+ }
+ 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;
}
}
- foreach (QString str, sortMap)
- completionList << str;
- nextCompletion = completionList.begin();
- lastCompletionLength = tabAbbrev.length();
-}
-
-void TabCompleter::ircUserJoinedOrParted(IrcUser *ircUser) {
- Q_UNUSED(ircUser)
- buildCompletionList();
+ _nextCompletion = _completionMap.begin();
+ _lastCompletionLength = tabAbbrev.length();
}
void TabCompleter::complete() {
- UiSettings uiSettings;
- nickSuffix = uiSettings.value("CompletionSuffix", QString(": ")).toString();
-
- if(!enabled) {
+ TabCompletionSettings s;
+ _nickSuffix = s.completionSuffix();
+
+ if(!_enabled) {
buildCompletionList();
- enabled = true;
+ _enabled = true;
}
-
- if (nextCompletion != completionList.end()) {
+
+ if (_nextCompletion != _completionMap.end()) {
// clear previous completion
- for (int i = 0; i < lastCompletionLength; i++) {
- inputLine->backspace();
+ for (int i = 0; i < _lastCompletionLength; i++) {
+ _lineEdit->backspace();
}
-
+
// insert completion
- inputLine->insert(*nextCompletion);
-
+ _lineEdit->insert(*_nextCompletion);
+
// remember charcount to delete next time and advance to next completion
- lastCompletionLength = nextCompletion->length();
- nextCompletion++;
-
+ _lastCompletionLength = _nextCompletion->length();
+ _nextCompletion++;
+
// we're completing the first word of the line
- if(inputLine->text().length() == lastCompletionLength) {
- inputLine->insert(nickSuffix);
- lastCompletionLength += nickSuffix.length();
+ if(_completionType == UserTab && _lineEdit->cursorPosition() == _lastCompletionLength) {
+ _lineEdit->insert(_nickSuffix);
+ _lastCompletionLength += _nickSuffix.length();
+ } else if (s.addSpaceMidSentence()) {
+ _lineEdit->insert(" ");
+ _lastCompletionLength++;
}
// we're at the end of the list -> start over again
} else {
- nextCompletion = completionList.begin();
+ if(!_completionMap.isEmpty()) {
+ _nextCompletion = _completionMap.begin();
+ complete();
+ }
}
-
}
void TabCompleter::reset() {
- enabled = false;
+ _enabled = false;
}
bool TabCompleter::eventFilter(QObject *obj, QEvent *event) {
- if(obj != inputLine || event->type() != QEvent::KeyPress)
+ if(obj != _lineEdit || event->type() != QEvent::KeyPress)
return QObject::eventFilter(obj, event);
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
-
- if(keyEvent->key() == Qt::Key_Tab) {
- complete();
- return true;
- } else {
+
+ if(keyEvent->key() != GraphicalUi::actionCollection("General")->action("TabCompletionKey")->shortcut()) {
reset();
- return false;
}
+ return false;
}
+// this determines the sort order
+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;
+
+ IrcUser *thatUser = _currentNetwork->ircUser(other.contents);
+ if(thatUser && _currentNetwork->isMe(thatUser))
+ return true;
+
+ if(!thisUser || !thatUser)
+ return QString::localeAwareCompare(this->contents, other.contents) < 0;
+
+ QDateTime thisSpokenTo = thisUser->lastSpokenTo(_currentBufferId);
+ QDateTime thatSpokenTo = thatUser->lastSpokenTo(_currentBufferId);
+
+ if(thisSpokenTo.isValid() || thatSpokenTo.isValid())
+ return thisSpokenTo > thatSpokenTo;
+
+ QDateTime thisTime = thisUser->lastChannelActivity(_currentBufferId);
+ QDateTime thatTime = thatUser->lastChannelActivity(_currentBufferId);
+
+ if(thisTime.isValid() || thatTime.isValid())
+ return thisTime > thatTime;
+ }
+ break;
+ case ChannelTab:
+ if(QString::compare(_currentBufferName, this->contents, Qt::CaseInsensitive) == 0)
+ return true;
+
+ if(QString::compare(_currentBufferName, other.contents, Qt::CaseInsensitive) == 0)
+ return false;
+ break;
+ default:
+ break;
+ }
+
+ return QString::localeAwareCompare(this->contents, other.contents) < 0;
+}