/* * Copyright (C) 2001-2008 Justin Karneges, Martin Hostettler * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // Generic tab completion support code. #include #include "tabcompletion.h" TabCompletion::TabCompletion(QObject *parent) : QObject(parent) { typingStatus_ = Typing_Normal; textEdit_ = 0; } TabCompletion::~TabCompletion() { } void TabCompletion::setTextEdit(QTextEdit* textEdit) { textEdit_ = textEdit; QColor mleBackground(textEdit_->palette().color(QPalette::Active, QPalette::Base)); if (mleBackground.value() < 128) { highlight_ = mleBackground.lighter(125); } else { highlight_ = mleBackground.darker(125); } } QTextEdit* TabCompletion::getTextEdit() { return textEdit_; } void TabCompletion::highlight(bool set) { Q_UNUSED(set); /* if (set) { QTextEdit::ExtraSelection es; es.cursor = replacementCursor_; es.format.setBackground(highlight_); textEdit_->setExtraSelections(QList() << es); } else { if (textEdit_) textEdit_->setExtraSelections(QList()); }*/ } void TabCompletion::moveCursorToOffset(QTextCursor &cur, int offset, QTextCursor::MoveMode mode) { cur.movePosition(QTextCursor::Start, mode); for (int i = 0; i < offset; i++) { // some sane limit on iterations if (cur.position() >= offset) break; // done our work if (!cur.movePosition(QTextCursor::NextCharacter, mode)) break; // failed? } } /** Find longest common (case insensitive) prefix of \a list. */ QString TabCompletion::longestCommonPrefix(QStringList list) { QString candidate = list.first().toLower(); int len = candidate.length(); while (len > 0) { bool found = true; foreach(QString str, list) { if (str.left(len).toLower() != candidate) { found = false; break; } } if (found) { break; } --len; candidate = candidate.left(len); } return candidate; } void TabCompletion::setup(QString text, int pos, int &start, int &end) { if (text.isEmpty() || pos==0) { atStart_ = true; toComplete_ = ""; start = 0; end = 0; return; } end = pos; int i; for (i = pos - 1; i > 0; --i) { if (text[i].isSpace()) { break; } } if (!text[i].isSpace()) { atStart_ = true; start = 0; } else { atStart_ = false; start = i+1; } toComplete_ = text.mid(start, end-start); } QString TabCompletion::suggestCompletion(bool *replaced) { suggestedCompletion_ = possibleCompletions(); suggestedIndex_ = -1; QString newText; if (suggestedCompletion_.count() == 1) { *replaced = true; newText = suggestedCompletion_.first(); } else if (suggestedCompletion_.count() > 1) { newText = longestCommonPrefix(suggestedCompletion_); if (newText.isEmpty()) { return toComplete_; // FIXME is this right? } typingStatus_ = Typing_MultipleSuggestions; // TODO: display a tooltip that will contain all suggestedCompletion *replaced = true; } return newText; } void TabCompletion::reset() { typingStatus_ = Typing_Normal; highlight(false); } /** Handle tab completion. * User interface uses a dual model, first tab completes upto the * longest common (case insensitiv) match, further tabbing cycles through all * possible completions. When doing a tab completion without something to complete * possibly offers a special guess first. */ void TabCompletion::tryComplete() { switch (typingStatus_) { case Typing_Normal: typingStatus_ = Typing_TabPressed; break; case Typing_TabPressed: typingStatus_ = Typing_TabbingCompletions; break; default: break; } QString newText; bool replaced = false; if (typingStatus_ == Typing_MultipleSuggestions) { if (!suggestedCompletion_.isEmpty()) { suggestedIndex_++; if (suggestedIndex_ >= (int)suggestedCompletion_.count()) { suggestedIndex_ = 0; } newText = suggestedCompletion_[suggestedIndex_]; replaced = true; } } else { QTextCursor cursor = textEdit_->textCursor(); QString wholeText = textEdit_->toPlainText(); int begin, end; setup(wholeText, cursor.position(), begin, end); replacementCursor_ = QTextCursor(textEdit_->document()); moveCursorToOffset(replacementCursor_, begin); moveCursorToOffset(replacementCursor_, end, QTextCursor::KeepAnchor); if (toComplete_.isEmpty() && typingStatus_ == Typing_TabbingCompletions) { typingStatus_ = Typing_MultipleSuggestions; QString guess; suggestedCompletion_ = allChoices(guess); if ( !guess.isEmpty() ) { suggestedIndex_ = -1; newText = guess; replaced = true; } else if (!suggestedCompletion_.isEmpty()) { suggestedIndex_ = 0; newText = suggestedCompletion_.first(); replaced = true; } } else { newText = suggestCompletion(&replaced); } } if (replaced) { textEdit_->setUpdatesEnabled(false); int start = qMin(replacementCursor_.anchor(), replacementCursor_.position()); replacementCursor_.beginEditBlock(); replacementCursor_.insertText(newText); replacementCursor_.endEditBlock(); QTextCursor newPos(replacementCursor_); moveCursorToOffset(replacementCursor_, start, QTextCursor::KeepAnchor); newPos.clearSelection(); textEdit_->setTextCursor(newPos); textEdit_->setUpdatesEnabled(true); textEdit_->viewport()->update(); } highlight(typingStatus_ == Typing_MultipleSuggestions); }