PageRenderTime 36ms CodeModel.GetById 23ms app.highlight 11ms RepoModel.GetById 0ms app.codeStats 1ms

/quassel-0.7.3/src/uisupport/tabcompleter.cpp

#
C++ | 204 lines | 143 code | 32 blank | 29 comment | 41 complexity | c9850d3554b18e898c8f2668f6349354 MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0
  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
 34const Network *TabCompleter::_currentNetwork;
 35BufferId TabCompleter::_currentBufferId;
 36QString TabCompleter::_currentBufferName;
 37TabCompleter::Type TabCompleter::_completionType;
 38
 39TabCompleter::TabCompleter(MultiLineEdit *_lineEdit)
 40  : QObject(_lineEdit),
 41    _lineEdit(_lineEdit),
 42    _enabled(false),
 43    _nickSuffix(": ")
 44{
 45  _lineEdit->installEventFilter(this);
 46}
 47
 48void 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
107void 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
144void TabCompleter::reset() {
145  _enabled = false;
146}
147
148bool 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
164bool 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}