| 
									
										
										
										
											2008-12-02 12:01:29 +01:00
										 |  |  | /***************************************************************************
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** This file is part of Qt Creator | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Contact:  Qt Software Information (qt-info@nokia.com) | 
					
						
							|  |  |  | ** | 
					
						
							| 
									
										
										
										
											2008-12-02 14:17:16 +01:00
										 |  |  | ** | 
					
						
							|  |  |  | ** Non-Open Source Usage | 
					
						
							|  |  |  | ** | 
					
						
							| 
									
										
										
										
											2008-12-02 12:01:29 +01:00
										 |  |  | ** Licensees may use this file in accordance with the Qt Beta Version | 
					
						
							|  |  |  | ** License Agreement, Agreement version 2.2 provided with the Software or, | 
					
						
							|  |  |  | ** alternatively, in accordance with the terms contained in a written | 
					
						
							| 
									
										
										
										
											2008-12-02 14:17:16 +01:00
										 |  |  | ** agreement between you and Nokia. | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** GNU General Public License Usage | 
					
						
							|  |  |  | ** | 
					
						
							| 
									
										
										
										
											2008-12-02 12:01:29 +01:00
										 |  |  | ** Alternatively, this file may be used under the terms of the GNU General | 
					
						
							|  |  |  | ** Public License versions 2.0 or 3.0 as published by the Free Software | 
					
						
							|  |  |  | ** Foundation and appearing in the file LICENSE.GPL included in the packaging | 
					
						
							|  |  |  | ** of this file.  Please review the following information to ensure GNU | 
					
						
							|  |  |  | ** General Public Licensing requirements will be met: | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
 | 
					
						
							|  |  |  | ** http://www.gnu.org/copyleft/gpl.html.
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** In addition, as a special exception, Nokia gives you certain additional | 
					
						
							| 
									
										
										
										
											2008-12-02 14:17:16 +01:00
										 |  |  | ** rights. These rights are described in the Nokia Qt GPL Exception | 
					
						
							|  |  |  | ** version 1.2, included in the file GPL_EXCEPTION.txt in this package. | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ***************************************************************************/ | 
					
						
							| 
									
										
										
										
											2008-12-02 12:01:29 +01:00
										 |  |  | #include "completionwidget.h"
 | 
					
						
							|  |  |  | #include "completionsupport.h"
 | 
					
						
							|  |  |  | #include "icompletioncollector.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <texteditor/itexteditable.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QtCore/QEvent>
 | 
					
						
							|  |  |  | #include <QtGui/QKeyEvent>
 | 
					
						
							|  |  |  | #include <QtGui/QApplication>
 | 
					
						
							|  |  |  | #include <QtGui/QVBoxLayout>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <limits.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using namespace TextEditor; | 
					
						
							|  |  |  | using namespace TextEditor::Internal; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define NUMBER_OF_VISIBLE_ITEMS 10
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class AutoCompletionModel : public QAbstractListModel | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     AutoCompletionModel(QObject *parent, const QList<CompletionItem> &items); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     inline const CompletionItem &itemAt(const QModelIndex &index) const | 
					
						
							|  |  |  |     { return m_items.at(index.row()); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     void setItems(const QList<CompletionItem> &items); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int rowCount(const QModelIndex &parent = QModelIndex()) const; | 
					
						
							|  |  |  |     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |     QList<CompletionItem> m_items; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AutoCompletionModel::AutoCompletionModel(QObject *parent, const QList<CompletionItem> &items) | 
					
						
							|  |  |  |     : QAbstractListModel(parent) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_items = items; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AutoCompletionModel::setItems(const QList<CompletionItem> &items) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_items = items; | 
					
						
							|  |  |  |     reset(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int AutoCompletionModel::rowCount(const QModelIndex &) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_items.count(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (index.row() >= m_items.count()) | 
					
						
							|  |  |  |         return QVariant(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (role == Qt::DisplayRole) { | 
					
						
							|  |  |  |         return itemAt(index).m_text; | 
					
						
							|  |  |  |     } else if (role == Qt::DecorationRole) { | 
					
						
							|  |  |  |         return itemAt(index).m_icon; | 
					
						
							|  |  |  |     } else if (role == Qt::ToolTipRole) { | 
					
						
							|  |  |  |         return itemAt(index).m_details; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return QVariant(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CompletionWidget::CompletionWidget(CompletionSupport *support, ITextEditable *editor) | 
					
						
							|  |  |  |     : QListView(), | 
					
						
							|  |  |  |       m_blockFocusOut(false), | 
					
						
							|  |  |  |       m_editor(editor), | 
					
						
							|  |  |  |       m_editorWidget(editor->widget()), | 
					
						
							|  |  |  |       m_model(0), | 
					
						
							|  |  |  |       m_support(support) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Q_ASSERT(m_editorWidget); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setUniformItemSizes(true); | 
					
						
							|  |  |  |     setSelectionBehavior(QAbstractItemView::SelectItems); | 
					
						
							|  |  |  |     setSelectionMode(QAbstractItemView::SingleSelection); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     connect(this, SIGNAL(activated(const QModelIndex &)), | 
					
						
							|  |  |  |             this, SLOT(completionActivated(const QModelIndex &))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // We disable the frame on this list view and use a QFrame around it instead.
 | 
					
						
							|  |  |  |     // This fixes the missing frame on Mac and improves the look with QGTKStyle.
 | 
					
						
							|  |  |  |     m_popupFrame = new QFrame(0, Qt::Popup); | 
					
						
							|  |  |  |     m_popupFrame->setFrameStyle(frameStyle()); | 
					
						
							|  |  |  |     setFrameStyle(QFrame::NoFrame); | 
					
						
							|  |  |  |     setParent(m_popupFrame); | 
					
						
							|  |  |  |     m_popupFrame->setObjectName("m_popupFrame"); | 
					
						
							|  |  |  |     m_popupFrame->setAttribute(Qt::WA_DeleteOnClose); | 
					
						
							|  |  |  |     QVBoxLayout *layout = new QVBoxLayout(m_popupFrame); | 
					
						
							|  |  |  |     layout->setMargin(0); | 
					
						
							|  |  |  |     layout->addWidget(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool CompletionWidget::event(QEvent *e) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_blockFocusOut) | 
					
						
							|  |  |  |         return QListView::event(e); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool forwardKeys = true; | 
					
						
							|  |  |  |     if (e->type() == QEvent::FocusOut) { | 
					
						
							|  |  |  |         closeList(); | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } else if (e->type() == QEvent::KeyPress) { | 
					
						
							|  |  |  |         QKeyEvent *ke = static_cast<QKeyEvent *>(e); | 
					
						
							|  |  |  |         switch (ke->key()) { | 
					
						
							|  |  |  |         case Qt::Key_Escape: | 
					
						
							|  |  |  |             closeList(); | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         case Qt::Key_Right: | 
					
						
							|  |  |  |         case Qt::Key_Left: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case Qt::Key_Tab: | 
					
						
							|  |  |  |         case Qt::Key_Return: | 
					
						
							|  |  |  |             //independently from style, accept current entry if return is pressed
 | 
					
						
							|  |  |  |             closeList(currentIndex()); | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         case Qt::Key_Up: | 
					
						
							|  |  |  |         case Qt::Key_Down: | 
					
						
							|  |  |  |         case Qt::Key_Enter: | 
					
						
							|  |  |  |         case Qt::Key_PageDown: | 
					
						
							|  |  |  |         case Qt::Key_PageUp: | 
					
						
							|  |  |  |             forwardKeys = false; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (forwardKeys) { | 
					
						
							|  |  |  |             m_blockFocusOut = true; | 
					
						
							|  |  |  |             QApplication::sendEvent(m_editorWidget, e); | 
					
						
							|  |  |  |             m_blockFocusOut = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Have the completion support update the list of items
 | 
					
						
							|  |  |  |             m_support->autoComplete(m_editor, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return QListView::event(e); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::keyboardSearch(const QString &search) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Q_UNUSED(search); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::closeList(const QModelIndex &index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_blockFocusOut = true; | 
					
						
							|  |  |  |     if (index.isValid()) | 
					
						
							|  |  |  |         emit itemSelected(m_model->itemAt(index)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     close(); | 
					
						
							|  |  |  |     if (m_popupFrame) { | 
					
						
							|  |  |  |         m_popupFrame->close(); | 
					
						
							|  |  |  |         m_popupFrame = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     emit completionListClosed(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_blockFocusOut = false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_model) { | 
					
						
							|  |  |  |         m_model = new AutoCompletionModel(this, completionItems); | 
					
						
							|  |  |  |         setModel(m_model); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         m_model->setItems(completionItems); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Select the first of the most relevant completion items
 | 
					
						
							|  |  |  |     int relevance = INT_MIN; | 
					
						
							|  |  |  |     int mostRelevantIndex = 0; | 
					
						
							|  |  |  |     for (int i = 0; i < completionItems.size(); ++i) { | 
					
						
							|  |  |  |         const CompletionItem &item = completionItems.at(i); | 
					
						
							|  |  |  |         if (item.m_relevance > relevance) { | 
					
						
							|  |  |  |             relevance = item.m_relevance; | 
					
						
							|  |  |  |             mostRelevantIndex = i; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setCurrentIndex(m_model->index(mostRelevantIndex)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::showCompletions(int startPos) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const QPoint &pos = m_editor->cursorRect(startPos).bottomLeft(); | 
					
						
							|  |  |  |     m_popupFrame->move(pos.x() - 16, pos.y()); | 
					
						
							|  |  |  |     m_popupFrame->setMinimumSize(1, 1); | 
					
						
							|  |  |  |     setMinimumSize(1, 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     updateSize(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_popupFrame->show(); | 
					
						
							|  |  |  |     show(); | 
					
						
							|  |  |  |     setFocus(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::updateSize() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int visibleItems = m_model->rowCount(); | 
					
						
							|  |  |  |     if (visibleItems > NUMBER_OF_VISIBLE_ITEMS) | 
					
						
							|  |  |  |         visibleItems = NUMBER_OF_VISIBLE_ITEMS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const QStyleOptionViewItem &option = viewOptions(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QSize shint; | 
					
						
							|  |  |  |     for (int i = 0; i < visibleItems; ++i) { | 
					
						
							|  |  |  |         QSize tmp = itemDelegate()->sizeHint(option, m_model->index(i)); | 
					
						
							|  |  |  |         if (shint.width() < tmp.width()) | 
					
						
							|  |  |  |             shint = tmp; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const int width = (shint.width() + (m_popupFrame->frameWidth() * 2) + 30); | 
					
						
							|  |  |  |     const int height = (shint.height() * visibleItems) + m_popupFrame->frameWidth() * 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_popupFrame->resize(width, height); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CompletionWidget::completionActivated(const QModelIndex &index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     closeList(index); | 
					
						
							|  |  |  | } |