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