1 /***************************************************************************
2 * Copyright (C) 2005-09 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include "tabcompleter.h"
23 #include "buffermodel.h"
25 #include "ircchannel.h"
27 #include "multilineedit.h"
29 #include "networkmodel.h"
30 #include "uisettings.h"
32 #include "actioncollection.h"
33 #include "graphicalui.h"
37 const Network *TabCompleter::_currentNetwork;
38 BufferId TabCompleter::_currentBufferId;
39 QString TabCompleter::_currentBufferName;
40 TabCompleter::Type TabCompleter::_completionType;
42 TabCompleter::TabCompleter(MultiLineEdit *_lineEdit)
48 // use both an Action and generic eventFilter, to make the shortcut configurable
49 // yet still be able to reset() when required
50 _lineEdit->installEventFilter(this);
51 ActionCollection *coll = GraphicalUi::actionCollection("General");
52 coll->addAction("TabCompletionKey", new Action(tr("Tab completion"), coll,
53 this, SLOT(onTabCompletionKey()), QKeySequence(Qt::Key_Tab)));
56 void TabCompleter::onTabCompletionKey() {
60 void TabCompleter::buildCompletionList() {
61 // ensure a safe state in case we return early.
62 _completionMap.clear();
63 _nextCompletion = _completionMap.begin();
65 // this is the first time tab is pressed -> build up the completion list and it's iterator
66 QModelIndex currentIndex = Client::bufferModel()->currentIndex();
67 _currentBufferId = currentIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
68 if(!_currentBufferId.isValid())
71 NetworkId networkId = currentIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>();
72 _currentBufferName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
74 _currentNetwork = Client::network(networkId);
78 QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(QRegExp("[^#\\w\\d-_\\[\\]{}|`^.\\\\]"),-1,-1);
79 QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
81 // channel completion - add all channels of the current network to the map
82 if(tabAbbrev.startsWith('#')) {
83 _completionType = ChannelTab;
84 foreach(IrcChannel *ircChannel, _currentNetwork->ircChannels()) {
85 if(regex.indexIn(ircChannel->name()) > -1)
86 _completionMap[ircChannel->name()] = ircChannel->name();
90 _completionType = UserTab;
91 switch(static_cast<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
92 case BufferInfo::ChannelBuffer:
93 { // scope is needed for local var declaration
94 IrcChannel *channel = _currentNetwork->ircChannel(_currentBufferName);
97 foreach(IrcUser *ircUser, channel->ircUsers()) {
98 if(regex.indexIn(ircUser->nick()) > -1)
99 _completionMap[ircUser->nick().toLower()] = ircUser->nick();
103 case BufferInfo::QueryBuffer:
104 if(regex.indexIn(_currentBufferName) > -1)
105 _completionMap[_currentBufferName.toLower()] = _currentBufferName;
106 case BufferInfo::StatusBuffer:
107 if(!_currentNetwork->myNick().isEmpty() && regex.indexIn(_currentNetwork->myNick()) > -1)
108 _completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick();
115 _nextCompletion = _completionMap.begin();
116 _lastCompletionLength = tabAbbrev.length();
119 void TabCompleter::complete() {
120 TabCompletionSettings s;
121 _nickSuffix = s.completionSuffix();
124 buildCompletionList();
128 if (_nextCompletion != _completionMap.end()) {
129 // clear previous completion
130 for (int i = 0; i < _lastCompletionLength; i++) {
131 _lineEdit->backspace();
135 _lineEdit->insert(*_nextCompletion);
137 // remember charcount to delete next time and advance to next completion
138 _lastCompletionLength = _nextCompletion->length();
141 // we're completing the first word of the line
142 if(_completionType == UserTab && _lineEdit->cursorPosition() == _lastCompletionLength) {
143 _lineEdit->insert(_nickSuffix);
144 _lastCompletionLength += _nickSuffix.length();
145 } else if (s.addSpaceMidSentence()) {
146 _lineEdit->insert(" ");
147 _lastCompletionLength++;
150 // we're at the end of the list -> start over again
152 if(!_completionMap.isEmpty()) {
153 _nextCompletion = _completionMap.begin();
159 void TabCompleter::reset() {
163 bool TabCompleter::eventFilter(QObject *obj, QEvent *event) {
164 if(obj != _lineEdit || event->type() != QEvent::KeyPress)
165 return QObject::eventFilter(obj, event);
167 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
169 if(keyEvent->key() != GraphicalUi::actionCollection("General")->action("TabCompletionKey")->shortcut()) {
175 // this determines the sort order
176 bool TabCompleter::CompletionKey::operator<(const CompletionKey &other) const {
177 switch(_completionType) {
180 IrcUser *thisUser = _currentNetwork->ircUser(this->contents);
181 if(thisUser && _currentNetwork->isMe(thisUser))
184 IrcUser *thatUser = _currentNetwork->ircUser(other.contents);
185 if(thatUser && _currentNetwork->isMe(thatUser))
188 if(!thisUser || !thatUser)
189 return QString::localeAwareCompare(this->contents, other.contents) < 0;
191 QDateTime thisSpokenTo = thisUser->lastSpokenTo(_currentBufferId);
192 QDateTime thatSpokenTo = thatUser->lastSpokenTo(_currentBufferId);
194 if(thisSpokenTo.isValid() || thatSpokenTo.isValid())
195 return thisSpokenTo > thatSpokenTo;
197 QDateTime thisTime = thisUser->lastChannelActivity(_currentBufferId);
198 QDateTime thatTime = thatUser->lastChannelActivity(_currentBufferId);
200 if(thisTime.isValid() || thatTime.isValid())
201 return thisTime > thatTime;
205 if(QString::compare(_currentBufferName, this->contents, Qt::CaseInsensitive) == 0)
208 if(QString::compare(_currentBufferName, other.contents, Qt::CaseInsensitive) == 0)
215 return QString::localeAwareCompare(this->contents, other.contents) < 0;