completionwidget.cpp 11.5 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
31
32
33
34
#include "completionwidget.h"
#include "completionsupport.h"
#include "icompletioncollector.h"

#include <texteditor/itexteditable.h>
hjk's avatar
hjk committed
35
#include <utils/qtcassert.h>
con's avatar
con committed
36
37
38

#include <QtCore/QEvent>
#include <QtGui/QApplication>
39
40
#include <QtGui/QDesktopWidget>
#include <QtGui/QKeyEvent>
con's avatar
con committed
41
#include <QtGui/QVBoxLayout>
con's avatar
con committed
42
#include <QtGui/QScrollBar>
con's avatar
con committed
43
44
45
46
47
48
49
50

#include <limits.h>

using namespace TextEditor;
using namespace TextEditor::Internal;

#define NUMBER_OF_VISIBLE_ITEMS 10

51
52
53
namespace TextEditor {
namespace Internal {

con's avatar
con committed
54
55
56
class AutoCompletionModel : public QAbstractListModel
{
public:
57
    AutoCompletionModel(QObject *parent);
con's avatar
con committed
58
59
60
61
62
63
64
65
66
67
68
69
70

    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;
};

71
72
73
74
75
} // namespace Internal
} // namespace TextEditor


AutoCompletionModel::AutoCompletionModel(QObject *parent)
con's avatar
con committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    : QAbstractListModel(parent)
{
}

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) {
97
        return itemAt(index).text;
con's avatar
con committed
98
    } else if (role == Qt::DecorationRole) {
99
        return itemAt(index).icon;
con's avatar
con committed
100
    } else if (role == Qt::ToolTipRole) {
101
        return itemAt(index).details;
con's avatar
con committed
102
103
104
105
106
    }

    return QVariant();
}

107

con's avatar
con committed
108
CompletionWidget::CompletionWidget(CompletionSupport *support, ITextEditable *editor)
109
110
    : QFrame(0, Qt::Popup),
      m_support(support),
111
112
      m_editor(editor),
      m_completionListView(new CompletionListView(support, editor, this))
113
114
115
116
{
    // We disable the frame on this list view and use a QFrame around it instead.
    // This improves the look with QGTKStyle.
#ifndef Q_WS_MAC
117
    setFrameStyle(m_completionListView->frameStyle());
118
#endif
119
    m_completionListView->setFrameStyle(QFrame::NoFrame);
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213

    setObjectName(QLatin1String("m_popupFrame"));
    setAttribute(Qt::WA_DeleteOnClose);
    setMinimumSize(1, 1);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setMargin(0);

    layout->addWidget(m_completionListView);
    setFocusProxy(m_completionListView);

    connect(m_completionListView, SIGNAL(itemSelected(TextEditor::CompletionItem)),
            this, SIGNAL(itemSelected(TextEditor::CompletionItem)));
    connect(m_completionListView, SIGNAL(completionListClosed()),
            this, SIGNAL(completionListClosed()));
    connect(m_completionListView, SIGNAL(activated(QModelIndex)),
            SLOT(closeList(QModelIndex)));
}

CompletionWidget::~CompletionWidget()
{
}

void CompletionWidget::setQuickFix(bool quickFix)
{
    m_completionListView->setQuickFix(quickFix);
}

void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems)
{
    m_completionListView->setCompletionItems(completionitems);
}

void CompletionWidget::closeList(const QModelIndex &index)
{
    m_completionListView->closeList(index);
    close();
}

void CompletionWidget::showCompletions(int startPos)
{
    updatePositionAndSize(startPos);
    show();
    setFocus();
}

void CompletionWidget::updatePositionAndSize(int startPos)
{
    // Determine size by calculating the space of the visible items
    QAbstractItemModel *model = m_completionListView->model();
    int visibleItems = model->rowCount();
    if (visibleItems > NUMBER_OF_VISIBLE_ITEMS)
        visibleItems = NUMBER_OF_VISIBLE_ITEMS;

    const QStyleOptionViewItem &option = m_completionListView->viewOptions();

    QSize shint;
    for (int i = 0; i < visibleItems; ++i) {
        QSize tmp = m_completionListView->itemDelegate()->sizeHint(option, model->index(i, 0));
        if (shint.width() < tmp.width())
            shint = tmp;
    }

    const int fw = frameWidth();
    const int width = shint.width() + fw * 2 + 30;
    const int height = shint.height() * visibleItems + fw * 2;

    // Determine the position, keeping the popup on the screen
    const QRect cursorRect = m_editor->cursorRect(startPos);
    const QDesktopWidget *desktop = QApplication::desktop();

    QWidget *editorWidget = m_editor->widget();

#ifdef Q_WS_MAC
    const QRect screen = desktop->availableGeometry(desktop->screenNumber(editorWidget));
#else
    const QRect screen = desktop->screenGeometry(desktop->screenNumber(editorWidget));
#endif

    QPoint pos = cursorRect.bottomLeft();
    pos.rx() -= 16 + fw;    // Space for the icons

    if (pos.y() + height > screen.bottom())
        pos.setY(cursorRect.top() - height);

    if (pos.x() + width > screen.right())
        pos.setX(screen.right() - width);

    setGeometry(pos.x(), pos.y(), width, height);
}


CompletionListView::CompletionListView(CompletionSupport *support, ITextEditable *editor, CompletionWidget *completionWidget)
    : QListView(completionWidget),
con's avatar
con committed
214
      m_blockFocusOut(false),
Roberto Raggi's avatar
Roberto Raggi committed
215
      m_quickFix(false),
con's avatar
con committed
216
217
      m_editor(editor),
      m_editorWidget(editor->widget()),
218
219
      m_completionWidget(completionWidget),
      m_model(new AutoCompletionModel(this)),
con's avatar
con committed
220
221
      m_support(support)
{
hjk's avatar
hjk committed
222
    QTC_ASSERT(m_editorWidget, return);
con's avatar
con committed
223

224
    setAttribute(Qt::WA_MacShowFocusRect, false);
con's avatar
con committed
225
226
227
228
    setUniformItemSizes(true);
    setSelectionBehavior(QAbstractItemView::SelectItems);
    setSelectionMode(QAbstractItemView::SingleSelection);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
229
    setMinimumSize(1, 1);
230
    setModel(m_model);
con's avatar
con committed
231
232
233
234
235
236
#ifdef Q_WS_MAC
    if (horizontalScrollBar())
        horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
    if (verticalScrollBar())
        verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
#endif
237
238
239
240
}

CompletionListView::~CompletionListView()
{
con's avatar
con committed
241
242
}

243
bool CompletionListView::event(QEvent *e)
con's avatar
con committed
244
245
246
247
248
249
{
    if (m_blockFocusOut)
        return QListView::event(e);

    bool forwardKeys = true;
    if (e->type() == QEvent::FocusOut) {
250
251
252
253
254
255
256
257
258
        QModelIndex index;
#if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA)
        QFocusEvent *fe = static_cast<QFocusEvent *>(e);
        if (fe->reason() == Qt::OtherFocusReason) {
            // Qt/carbon workaround
            // focus out is received before the key press event.
            index = currentIndex();
        }
#endif
259
        m_completionWidget->closeList(index);
con's avatar
con committed
260
        return true;
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    } else if (e->type() == QEvent::ShortcutOverride) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion
            if (ke->modifiers() == Qt::ControlModifier)
            {
                e->accept();
                int change = (ke->key() == Qt::Key_N) ? 1 : -1;
                int nrows = model()->rowCount();
                int row = currentIndex().row();
                int newRow = (row + change + nrows) % nrows;
                if (newRow == row + change || !ke->isAutoRepeat())
                    setCurrentIndex(m_model->index(newRow));
                return true;
            }
        }
con's avatar
con committed
279
280
281
    } else if (e->type() == QEvent::KeyPress) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
282
283
284
285
286
287
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion - so don't pass on to editor
            if (ke->modifiers() == Qt::ControlModifier)
                forwardKeys = false;
            break;
288

con's avatar
con committed
289
        case Qt::Key_Escape:
290
            m_completionWidget->closeList();
con's avatar
con committed
291
            return true;
292

con's avatar
con committed
293
294
295
        case Qt::Key_Right:
        case Qt::Key_Left:
            break;
296

con's avatar
con committed
297
298
299
        case Qt::Key_Tab:
        case Qt::Key_Return:
            //independently from style, accept current entry if return is pressed
300
            if (qApp->focusWidget() == this)
301
                m_completionWidget->closeList(currentIndex());
con's avatar
con committed
302
            return true;
303

con's avatar
con committed
304
        case Qt::Key_Up:
305
306
            if (!ke->isAutoRepeat()
                && currentIndex().row() == 0) {
307
308
309
310
311
                setCurrentIndex(model()->index(model()->rowCount()-1, 0));
                return true;
            }
            forwardKeys = false;
            break;
312

con's avatar
con committed
313
        case Qt::Key_Down:
314
315
            if (!ke->isAutoRepeat()
                && currentIndex().row() == model()->rowCount()-1) {
316
317
318
                setCurrentIndex(model()->index(0, 0));
                return true;
            }
319
320
321
            forwardKeys = false;
            break;

con's avatar
con committed
322
323
324
325
326
        case Qt::Key_Enter:
        case Qt::Key_PageDown:
        case Qt::Key_PageUp:
            forwardKeys = false;
            break;
327

con's avatar
con committed
328
        default:
329
330
331
            // if a key is forwarded, completion widget is re-opened and selected item is reset to first,
            // so only forward keys that insert text and refine the completed item
            forwardKeys = !ke->text().isEmpty();
con's avatar
con committed
332
333
334
            break;
        }

Roberto Raggi's avatar
Roberto Raggi committed
335
        if (forwardKeys && ! m_quickFix) {
con's avatar
con committed
336
337
338
339
340
341
342
343
344
345
346
347
348
            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);
}

349
void CompletionListView::keyboardSearch(const QString &search)
con's avatar
con committed
350
{
351
    Q_UNUSED(search)
con's avatar
con committed
352
353
}

354
void CompletionListView::setQuickFix(bool quickFix)
Roberto Raggi's avatar
Roberto Raggi committed
355
356
357
358
{
    m_quickFix = quickFix;
}

359
void CompletionListView::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems)
con's avatar
con committed
360
{
361
    m_model->setItems(completionItems);
con's avatar
con committed
362
363
364
365
366
367

    // 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);
368
369
        if (item.relevance > relevance) {
            relevance = item.relevance;
con's avatar
con committed
370
371
372
373
374
375
376
            mostRelevantIndex = i;
        }
    }

    setCurrentIndex(m_model->index(mostRelevantIndex));
}

377
void CompletionListView::closeList(const QModelIndex &index)
con's avatar
con committed
378
{
379
    m_blockFocusOut = true;
380

381
382
    if (index.isValid())
        emit itemSelected(m_model->itemAt(index));
con's avatar
con committed
383

384
    emit completionListClosed();
con's avatar
con committed
385

386
    m_blockFocusOut = false;
con's avatar
con committed
387
}