OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / completionwidget.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "completionwidget.h"
34 #include "completionsupport.h"
35 #include "icompletioncollector.h"
36
37 #include <texteditor/itexteditor.h>
38
39 #include <utils/faketooltip.h>
40 #include <utils/qtcassert.h>
41
42 #include <QtCore/QEvent>
43 #include <QtGui/QApplication>
44 #include <QtGui/QDesktopWidget>
45 #include <QtGui/QKeyEvent>
46 #include <QtGui/QVBoxLayout>
47 #include <QtGui/QScrollBar>
48 #include <QtGui/QLabel>
49 #include <QtGui/QStylePainter>
50 #include <QtGui/QToolTip>
51
52 #include <limits.h>
53
54 using namespace TextEditor;
55 using namespace TextEditor::Internal;
56
57 #define NUMBER_OF_VISIBLE_ITEMS 10
58
59 namespace TextEditor {
60 namespace Internal {
61
62 class AutoCompletionModel : public QAbstractListModel
63 {
64 public:
65     AutoCompletionModel(QObject *parent);
66
67     inline const CompletionItem &itemAt(const QModelIndex &index) const
68     { return m_items.at(index.row()); }
69
70     void setItems(const QList<CompletionItem> &items);
71
72     int rowCount(const QModelIndex &parent = QModelIndex()) const;
73     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
74
75 private:
76     QList<CompletionItem> m_items;
77 };
78
79
80 class CompletionInfoFrame : public Utils::FakeToolTip
81 {
82 public:
83     CompletionInfoFrame(QWidget *parent = 0) :
84         Utils::FakeToolTip(parent),
85         m_label(new QLabel(this))
86     {
87         QVBoxLayout *layout = new QVBoxLayout(this);
88         layout->setMargin(0);
89         layout->setSpacing(0);
90         layout->addWidget(m_label);
91
92         m_label->setForegroundRole(QPalette::ToolTipText);
93         m_label->setBackgroundRole(QPalette::ToolTipBase);
94     }
95
96     void setText(const QString &text)
97     {
98         m_label->setText(text);
99     }
100
101 private:
102     QLabel *m_label;
103 };
104
105
106 } // namespace Internal
107 } // namespace TextEditor
108
109
110 AutoCompletionModel::AutoCompletionModel(QObject *parent)
111     : QAbstractListModel(parent)
112 {
113 }
114
115 void AutoCompletionModel::setItems(const QList<CompletionItem> &items)
116 {
117     m_items = items;
118     reset();
119 }
120
121 int AutoCompletionModel::rowCount(const QModelIndex &) const
122 {
123     return m_items.count();
124 }
125
126 QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const
127 {
128     if (index.row() >= m_items.count())
129         return QVariant();
130
131     if (role == Qt::DisplayRole) {
132         return itemAt(index).text;
133     } else if (role == Qt::DecorationRole) {
134         return itemAt(index).icon;
135     } else if (role == Qt::WhatsThisRole) {
136         return itemAt(index).details;
137     }
138
139     return QVariant();
140 }
141
142
143 CompletionWidget::CompletionWidget(CompletionSupport *support,
144         ITextEditor *editor)
145     : QFrame(0, Qt::Popup),
146       m_support(support),
147       m_editor(editor),
148       m_completionListView(new CompletionListView(support, editor, this))
149 {
150     // We disable the frame on this list view and use a QFrame around it instead.
151     // This improves the look with QGTKStyle.
152 #ifndef Q_WS_MAC
153     setFrameStyle(m_completionListView->frameStyle());
154 #endif
155     m_completionListView->setFrameStyle(QFrame::NoFrame);
156
157     setObjectName(QLatin1String("m_popupFrame"));
158     setAttribute(Qt::WA_DeleteOnClose);
159     setMinimumSize(1, 1);
160     setFont(editor->widget()->font());
161
162     QVBoxLayout *layout = new QVBoxLayout(this);
163     layout->setMargin(0);
164
165     layout->addWidget(m_completionListView);
166     setFocusProxy(m_completionListView);
167
168     connect(m_completionListView, SIGNAL(itemSelected(TextEditor::CompletionItem)),
169             this, SIGNAL(itemSelected(TextEditor::CompletionItem)));
170     connect(m_completionListView, SIGNAL(completionListClosed()),
171             this, SIGNAL(completionListClosed()));
172     connect(m_completionListView, SIGNAL(activated(QModelIndex)),
173             SLOT(closeList(QModelIndex)));
174     connect(editor, SIGNAL(contentsChangedBecauseOfUndo()),
175             this, SLOT(closeList()));
176 }
177
178 CompletionWidget::~CompletionWidget()
179 {
180 }
181
182 void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems)
183 {
184     m_completionListView->setCompletionItems(completionitems);
185 }
186
187 void CompletionWidget::closeList(const QModelIndex &index)
188 {
189     m_completionListView->closeList(index);
190     close();
191 }
192
193 void CompletionWidget::showCompletions(int startPos)
194 {
195     ensurePolished();
196     updatePositionAndSize(startPos);
197     show();
198     setFocus();
199 }
200
201 QChar CompletionWidget::typedChar() const
202 {
203     return m_completionListView->m_typedChar;
204 }
205
206 CompletionItem CompletionWidget::currentCompletionItem() const
207 {
208     return m_completionListView->currentCompletionItem();
209 }
210
211 bool CompletionWidget::explicitlySelected() const
212 {
213     return m_completionListView->explicitlySelected();
214 }
215
216 void CompletionWidget::setCurrentIndex(int index)
217 {
218     m_completionListView->setCurrentIndex(m_completionListView->model()->index(index, 0));
219 }
220
221 void CompletionWidget::updatePositionAndSize(int startPos)
222 {
223     // Determine size by calculating the space of the visible items
224     QAbstractItemModel *model = m_completionListView->model();
225     int visibleItems = model->rowCount();
226     if (visibleItems > NUMBER_OF_VISIBLE_ITEMS)
227         visibleItems = NUMBER_OF_VISIBLE_ITEMS;
228
229     const QStyleOptionViewItem &option = m_completionListView->viewOptions();
230
231     QSize shint;
232     for (int i = 0; i < visibleItems; ++i) {
233         QSize tmp = m_completionListView->itemDelegate()->sizeHint(option, model->index(i, 0));
234         if (shint.width() < tmp.width())
235             shint = tmp;
236     }
237
238     const int fw = frameWidth();
239     const int width = shint.width() + fw * 2 + 30;
240     const int height = shint.height() * visibleItems + fw * 2;
241
242     // Determine the position, keeping the popup on the screen
243     const QRect cursorRect = m_editor->cursorRect(startPos);
244     const QDesktopWidget *desktop = QApplication::desktop();
245
246     QWidget *editorWidget = m_editor->widget();
247
248 #ifdef Q_WS_MAC
249     const QRect screen = desktop->availableGeometry(desktop->screenNumber(editorWidget));
250 #else
251     const QRect screen = desktop->screenGeometry(desktop->screenNumber(editorWidget));
252 #endif
253
254     QPoint pos = cursorRect.bottomLeft();
255     pos.rx() -= 16 + fw;    // Space for the icons
256
257     if (pos.y() + height > screen.bottom())
258         pos.setY(cursorRect.top() - height);
259
260     if (pos.x() + width > screen.right())
261         pos.setX(screen.right() - width);
262
263     setGeometry(pos.x(), pos.y(), width, height);
264 }
265
266 CompletionListView::CompletionListView(CompletionSupport *support,
267         ITextEditor *editor, CompletionWidget *completionWidget)
268     : QListView(completionWidget),
269       m_blockFocusOut(false),
270       m_editor(editor),
271       m_editorWidget(editor->widget()),
272       m_completionWidget(completionWidget),
273       m_model(new AutoCompletionModel(this)),
274       m_support(support),
275       m_explicitlySelected(false)
276 {
277     QTC_ASSERT(m_editorWidget, return);
278
279     m_infoTimer.setInterval(1000);
280     m_infoTimer.setSingleShot(true);
281     connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip()));
282
283     setAttribute(Qt::WA_MacShowFocusRect, false);
284     setUniformItemSizes(true);
285     setSelectionBehavior(QAbstractItemView::SelectItems);
286     setSelectionMode(QAbstractItemView::SingleSelection);
287     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
288     setMinimumSize(1, 1);
289     setModel(m_model);
290 #ifdef Q_WS_MAC
291     if (horizontalScrollBar())
292         horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
293     if (verticalScrollBar())
294         verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
295 #endif
296 }
297
298 CompletionListView::~CompletionListView()
299 {
300 }
301
302 CompletionItem CompletionListView::currentCompletionItem() const
303 {
304     int row = currentIndex().row();
305     if (row >= 0 && row < m_model->rowCount())
306         return m_model->itemAt(currentIndex());
307
308     return CompletionItem();
309 }
310
311 bool CompletionListView::explicitlySelected() const
312 {
313     return m_explicitlySelected;
314 }
315
316 void CompletionListView::maybeShowInfoTip()
317 {
318     QModelIndex current = currentIndex();
319     if (!current.isValid())
320         return;
321     QString infoTip = current.data(Qt::WhatsThisRole).toString();
322
323     if (infoTip.isEmpty()) {
324         delete m_infoFrame.data();
325         m_infoTimer.setInterval(200);
326         return;
327     }
328
329     if (m_infoFrame.isNull())
330         m_infoFrame = new CompletionInfoFrame(this);
331
332
333     QRect r = rectForIndex(current);
334     m_infoFrame->move(
335             (parentWidget()->mapToGlobal(
336                     parentWidget()->rect().topRight())).x() + 3,
337             mapToGlobal(r.topRight()).y() - verticalOffset()
338             );
339     m_infoFrame->setText(infoTip);
340     m_infoFrame->adjustSize();
341     m_infoFrame->show();
342     m_infoFrame->raise();
343
344     m_infoTimer.setInterval(0);
345 }
346
347 void CompletionListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
348 {
349     QListView::currentChanged(current, previous);
350     m_infoTimer.start();
351 }
352
353
354 bool CompletionListView::event(QEvent *e)
355 {
356     if (m_blockFocusOut)
357         return QListView::event(e);
358
359     bool forwardKeys = true;
360     if (e->type() == QEvent::FocusOut) {
361         QModelIndex index;
362 #if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA)
363         QFocusEvent *fe = static_cast<QFocusEvent *>(e);
364         if (fe->reason() == Qt::OtherFocusReason) {
365             // Qt/carbon workaround
366             // focus out is received before the key press event.
367             index = currentIndex();
368         }
369 #endif
370         m_completionWidget->closeList(index);
371         if (m_infoFrame)
372             m_infoFrame->close();
373         return true;
374     } else if (e->type() == QEvent::ShortcutOverride) {
375         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
376         switch (ke->key()) {
377         case Qt::Key_N:
378         case Qt::Key_P:
379             // select next/previous completion
380             if (ke->modifiers() == Qt::ControlModifier)
381             {
382                 e->accept();
383                 int change = (ke->key() == Qt::Key_N) ? 1 : -1;
384                 int nrows = model()->rowCount();
385                 int row = currentIndex().row();
386                 int newRow = (row + change + nrows) % nrows;
387                 if (newRow == row + change || !ke->isAutoRepeat())
388                     setCurrentIndex(m_model->index(newRow));
389                 return true;
390             }
391         }
392     } else if (e->type() == QEvent::KeyPress) {
393         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
394         switch (ke->key()) {
395         case Qt::Key_N:
396         case Qt::Key_P:
397             // select next/previous completion - so don't pass on to editor
398             if (ke->modifiers() == Qt::ControlModifier)
399                 forwardKeys = false;
400             break;
401
402         case Qt::Key_Escape:
403             m_completionWidget->closeList();
404             return true;
405
406         case Qt::Key_Right:
407         case Qt::Key_Left:
408         case Qt::Key_Home:
409         case Qt::Key_End:
410             // We want these navigation keys to work in the editor, so forward them
411             break;
412
413         case Qt::Key_Tab:
414         case Qt::Key_Return:
415             //independently from style, accept current entry if return is pressed
416             if (qApp->focusWidget() == this)
417                 m_completionWidget->closeList(currentIndex());
418             return true;
419
420         case Qt::Key_Up:
421             m_explicitlySelected = true;
422             if (!ke->isAutoRepeat()
423                 && currentIndex().row() == 0) {
424                 setCurrentIndex(model()->index(model()->rowCount()-1, 0));
425                 return true;
426             }
427             forwardKeys = false;
428             break;
429
430         case Qt::Key_Down:
431             m_explicitlySelected = true;
432             if (!ke->isAutoRepeat()
433                 && currentIndex().row() == model()->rowCount()-1) {
434                 setCurrentIndex(model()->index(0, 0));
435                 return true;
436             }
437             forwardKeys = false;
438             break;
439
440         case Qt::Key_Enter:
441         case Qt::Key_PageDown:
442         case Qt::Key_PageUp:
443             forwardKeys = false;
444             break;
445
446         default:
447             // if a key is forwarded, completion widget is re-opened and selected item is reset to first,
448             // so only forward keys that insert text and refine the completed item
449             forwardKeys = !ke->text().isEmpty();
450             break;
451         }
452
453         const CompletionPolicy policy = m_support->policy();
454         if (forwardKeys && policy != QuickFixCompletion) {
455             if (ke->text().length() == 1 && currentIndex().isValid() && qApp->focusWidget() == this) {
456                 QChar typedChar = ke->text().at(0);
457                 const CompletionItem &item = m_model->itemAt(currentIndex());
458                 if (item.collector->typedCharCompletes(item, typedChar)) {
459                     m_typedChar = typedChar;
460                     m_completionWidget->closeList(currentIndex());
461                     return true;
462                 }
463             }
464
465             m_blockFocusOut = true;
466             QApplication::sendEvent(m_editorWidget, e);
467             m_blockFocusOut = false;
468
469             // Have the completion support update the list of items
470             m_support->complete(m_editor, policy, false);
471
472             return true;
473         }
474     }
475     return QListView::event(e);
476 }
477
478 void CompletionListView::keyboardSearch(const QString &search)
479 {
480     Q_UNUSED(search)
481 }
482
483 void CompletionListView::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems)
484 {
485     m_model->setItems(completionItems);
486     setCurrentIndex(m_model->index(0)); // Select the first item
487 }
488
489 void CompletionListView::closeList(const QModelIndex &index)
490 {
491     m_blockFocusOut = true;
492
493     if (index.isValid())
494         emit itemSelected(m_model->itemAt(index));
495
496     emit completionListClosed();
497
498     m_blockFocusOut = false;
499 }