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