40a4929411fd9632d133b1c9630b430c24951cb6
[quassel.git] / src / uisupport / tabcompleter.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2014 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "tabcompleter.h"
22
23 #include "buffermodel.h"
24 #include "client.h"
25 #include "ircchannel.h"
26 #include "ircuser.h"
27 #include "multilineedit.h"
28 #include "network.h"
29 #include "networkmodel.h"
30 #include "uisettings.h"
31 #include "action.h"
32 #include "actioncollection.h"
33 #include "graphicalui.h"
34
35 #include <QRegExp>
36
37 const Network *TabCompleter::_currentNetwork;
38 BufferId TabCompleter::_currentBufferId;
39 QString TabCompleter::_currentBufferName;
40 TabCompleter::Type TabCompleter::_completionType;
41
42 TabCompleter::TabCompleter(MultiLineEdit *_lineEdit)
43     : QObject(_lineEdit),
44     _lineEdit(_lineEdit),
45     _enabled(false),
46     _nickSuffix(": ")
47 {
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)));
54 }
55
56
57 void TabCompleter::onTabCompletionKey()
58 {
59     complete();
60 }
61
62
63 void TabCompleter::buildCompletionList()
64 {
65     // ensure a safe state in case we return early.
66     _completionMap.clear();
67     _nextCompletion = _completionMap.begin();
68
69     // this is the first time tab is pressed -> build up the completion list and it's iterator
70     QModelIndex currentIndex = Client::bufferModel()->currentIndex();
71     _currentBufferId = currentIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
72     if (!_currentBufferId.isValid())
73         return;
74
75     NetworkId networkId = currentIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>();
76     _currentBufferName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
77
78     _currentNetwork = Client::network(networkId);
79     if (!_currentNetwork)
80         return;
81
82     QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(QRegExp("[^#\\w\\d-_\\[\\]{}|`^.\\\\]"), -1, -1);
83     QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
84
85     // channel completion - add all channels of the current network to the map
86     if (tabAbbrev.startsWith('#')) {
87         _completionType = ChannelTab;
88         foreach(IrcChannel *ircChannel, _currentNetwork->ircChannels()) {
89             if (regex.indexIn(ircChannel->name()) > -1)
90                 _completionMap[ircChannel->name()] = ircChannel->name();
91         }
92     }
93     else {
94         // user completion
95         _completionType = UserTab;
96         switch (static_cast<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
97         case BufferInfo::ChannelBuffer:
98         { // scope is needed for local var declaration
99             IrcChannel *channel = _currentNetwork->ircChannel(_currentBufferName);
100             if (!channel)
101                 return;
102             foreach(IrcUser *ircUser, channel->ircUsers()) {
103                 if (regex.indexIn(ircUser->nick()) > -1)
104                     _completionMap[ircUser->nick().toLower()] = ircUser->nick();
105             }
106         }
107         break;
108         case BufferInfo::QueryBuffer:
109             if (regex.indexIn(_currentBufferName) > -1)
110                 _completionMap[_currentBufferName.toLower()] = _currentBufferName;
111         case BufferInfo::StatusBuffer:
112             if (!_currentNetwork->myNick().isEmpty() && regex.indexIn(_currentNetwork->myNick()) > -1)
113                 _completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick();
114             break;
115         default:
116             return;
117         }
118     }
119
120     _nextCompletion = _completionMap.begin();
121     _lastCompletionLength = tabAbbrev.length();
122 }
123
124
125 void TabCompleter::complete()
126 {
127     TabCompletionSettings s;
128     _nickSuffix = s.completionSuffix();
129
130     if (!_enabled) {
131         buildCompletionList();
132         _enabled = true;
133     }
134
135     if (_nextCompletion != _completionMap.end()) {
136         // clear previous completion
137         for (int i = 0; i < _lastCompletionLength; i++) {
138             _lineEdit->backspace();
139         }
140
141         // insert completion
142         _lineEdit->insert(*_nextCompletion);
143
144         // remember charcount to delete next time and advance to next completion
145         _lastCompletionLength = _nextCompletion->length();
146         _nextCompletion++;
147
148         // we're completing the first word of the line
149         if (_completionType == UserTab && _lineEdit->cursorPosition() == _lastCompletionLength) {
150             _lineEdit->insert(_nickSuffix);
151             _lastCompletionLength += _nickSuffix.length();
152         }
153         else if (s.addSpaceMidSentence()) {
154             _lineEdit->insert(" ");
155             _lastCompletionLength++;
156         }
157
158         // we're at the end of the list -> start over again
159     }
160     else {
161         if (!_completionMap.isEmpty()) {
162             _nextCompletion = _completionMap.begin();
163             complete();
164         }
165     }
166 }
167
168
169 void TabCompleter::reset()
170 {
171     _enabled = false;
172 }
173
174
175 bool TabCompleter::eventFilter(QObject *obj, QEvent *event)
176 {
177     if (obj != _lineEdit || event->type() != QEvent::KeyPress)
178         return QObject::eventFilter(obj, event);
179
180     QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
181
182     if (keyEvent->key() != GraphicalUi::actionCollection("General")->action("TabCompletionKey")->shortcut()) {
183         reset();
184     }
185     return false;
186 }
187
188
189 // this determines the sort order
190 bool TabCompleter::CompletionKey::operator<(const CompletionKey &other) const
191 {
192     switch (_completionType) {
193     case UserTab:
194     {
195         IrcUser *thisUser = _currentNetwork->ircUser(this->contents);
196         if (thisUser && _currentNetwork->isMe(thisUser))
197             return false;
198
199         IrcUser *thatUser = _currentNetwork->ircUser(other.contents);
200         if (thatUser && _currentNetwork->isMe(thatUser))
201             return true;
202
203         if (!thisUser || !thatUser)
204             return QString::localeAwareCompare(this->contents, other.contents) < 0;
205
206         QDateTime thisSpokenTo = thisUser->lastSpokenTo(_currentBufferId);
207         QDateTime thatSpokenTo = thatUser->lastSpokenTo(_currentBufferId);
208
209         if (thisSpokenTo.isValid() || thatSpokenTo.isValid())
210             return thisSpokenTo > thatSpokenTo;
211
212         QDateTime thisTime = thisUser->lastChannelActivity(_currentBufferId);
213         QDateTime thatTime = thatUser->lastChannelActivity(_currentBufferId);
214
215         if (thisTime.isValid() || thatTime.isValid())
216             return thisTime > thatTime;
217     }
218     break;
219     case ChannelTab:
220         if (QString::compare(_currentBufferName, this->contents, Qt::CaseInsensitive) == 0)
221             return true;
222
223         if (QString::compare(_currentBufferName, other.contents, Qt::CaseInsensitive) == 0)
224             return false;
225         break;
226     default:
227         break;
228     }
229
230     return QString::localeAwareCompare(this->contents, other.contents) < 0;
231 }