shortcutsettings.cpp 12.6 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 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
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
#include "shortcutsettings.h"
34
#include "actionmanager_p.h"
35
#include "actionmanager/command.h"
con's avatar
con committed
36
#include "command_p.h"
con's avatar
con committed
37
#include "commandsfile.h"
38
#include "coreconstants.h"
con's avatar
con committed
39
#include "filemanager.h"
40
#include "icore.h"
41
#include "id.h"
42

43
44
#include <utils/treewidgetcolumnstretcher.h>

con's avatar
con committed
45
46
47
48
#include <QtGui/QKeyEvent>
#include <QtGui/QShortcut>
#include <QtGui/QHeaderView>
#include <QtGui/QFileDialog>
49
#include <QtGui/QLineEdit>
50
51
52
#include <QtGui/QAction>
#include <QtGui/QKeyEvent>
#include <QtGui/QTreeWidgetItem>
53
#include <QtCore/QCoreApplication>
con's avatar
con committed
54
55
#include <QtDebug>

56
Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*)
con's avatar
con committed
57
58
59
60
61

using namespace Core;
using namespace Core::Internal;

ShortcutSettings::ShortcutSettings(QObject *parent)
62
    : CommandMappings(parent), m_initialized(false)
con's avatar
con committed
63
{
64
65
    Core::Internal::ActionManagerPrivate *am = ActionManagerPrivate::instance();
    connect(am, SIGNAL(commandListChanged()), this, SLOT(initialize()));
con's avatar
con committed
66
67
68
69
70
71
72
}

ShortcutSettings::~ShortcutSettings()
{
}

// IOptionsPage
73

74

75
76
QString ShortcutSettings::id() const
{
77
    return QLatin1String(Core::Constants::SETTINGS_ID_SHORTCUTS);
78
79
}

80
QString ShortcutSettings::displayName() const
con's avatar
con committed
81
82
83
84
85
86
{
    return tr("Keyboard");
}

QString ShortcutSettings::category() const
{
87
    return QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE);
con's avatar
con committed
88
89
}

90
QString ShortcutSettings::displayCategory() const
con's avatar
con committed
91
{
92
    return QCoreApplication::translate("Core", Core::Constants::SETTINGS_TR_CATEGORY_CORE);
con's avatar
con committed
93
94
}

95
96
97
98
99
QIcon ShortcutSettings::categoryIcon() const
{
    return QIcon(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE_ICON));
}

con's avatar
con committed
100
101
QWidget *ShortcutSettings::createPage(QWidget *parent)
{
102
    m_initialized = true;
con's avatar
con committed
103
104
    m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;

105
    QWidget *w = CommandMappings::createPage(parent);
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

    const QString pageTitle = tr("Keyboard Shortcuts");
    const QString targetLabelText = tr("Key sequence:");
    const QString editTitle = tr("Shortcut");

    setPageTitle(pageTitle);
    setTargetLabelText(targetLabelText);
    setTargetEditTitle(editTitle);
    setTargetHeader(editTitle);

    if (m_searchKeywords.isEmpty()) {
        QTextStream(&m_searchKeywords) << ' ' << pageTitle
                << ' ' << targetLabelText
                << ' ' << editTitle;
    }
con's avatar
con committed
121
122
123
124

    return w;
}

125
void ShortcutSettings::apply()
con's avatar
con committed
126
{
127
128
129
    foreach (ShortcutItem *item, m_scitems)
        item->m_cmd->setKeySequence(item->m_key);
}
con's avatar
con committed
130

131
132
void ShortcutSettings::finish()
{
con's avatar
con committed
133
134
    qDeleteAll(m_scitems);
    m_scitems.clear();
135

136
    CommandMappings::finish();
137
    m_initialized = false;
con's avatar
con committed
138
139
}

140
141
142
143
144
bool ShortcutSettings::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

con's avatar
con committed
145
146
bool ShortcutSettings::eventFilter(QObject *o, QEvent *e)
{
147
148
    Q_UNUSED(o)

con's avatar
con committed
149
150
151
152
153
154
155
    if ( e->type() == QEvent::KeyPress ) {
        QKeyEvent *k = static_cast<QKeyEvent*>(e);
        handleKeyEvent(k);
        return true;
    }

    if ( e->type() == QEvent::Shortcut ||
156
         e->type() == QEvent::KeyRelease ) {
con's avatar
con committed
157
        return true;
158
159
160
161
162
163
164
    }

    if (e->type() == QEvent::ShortcutOverride) {
        // for shortcut overrides, we need to accept as well
        e->accept();
        return true;
    }
con's avatar
con committed
165
166
167
168
169
170

    return false;
}

void ShortcutSettings::commandChanged(QTreeWidgetItem *current)
{
171
172
    CommandMappings::commandChanged(current);
    if (!current || !current->data(0, Qt::UserRole).isValid())
con's avatar
con committed
173
174
175
176
177
        return;
    ShortcutItem *scitem = qVariantValue<ShortcutItem *>(current->data(0, Qt::UserRole));
    setKeySequence(scitem->m_key);
}

178
void ShortcutSettings::targetIdentifierChanged()
con's avatar
con committed
179
{
180
    QTreeWidgetItem *current = commandList()->currentItem();
con's avatar
con committed
181
182
183
    if (current && current->data(0, Qt::UserRole).isValid()) {
        ShortcutItem *scitem = qVariantValue<ShortcutItem *>(current->data(0, Qt::UserRole));
        scitem->m_key = QKeySequence(m_key[0], m_key[1], m_key[2], m_key[3]);
184
185
186
187
        if (scitem->m_cmd->defaultKeySequence() != scitem->m_key)
            setModified(current, true);
        else
            setModified(current, false);
con's avatar
con committed
188
        current->setText(2, scitem->m_key);
189
        resetCollisionMarker(scitem);
190
        markPossibleCollisions(scitem);
con's avatar
con committed
191
192
193
194
195
196
197
198
199
200
    }
}

void ShortcutSettings::setKeySequence(const QKeySequence &key)
{
    m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
    m_keyNum = key.count();
    for (int i = 0; i < m_keyNum; ++i) {
        m_key[i] = key[i];
    }
201
    targetEdit()->setText(key);
con's avatar
con committed
202
203
}

204
void ShortcutSettings::resetTargetIdentifier()
con's avatar
con committed
205
{
206
    QTreeWidgetItem *current = commandList()->currentItem();
con's avatar
con committed
207
208
209
210
211
212
    if (current && current->data(0, Qt::UserRole).isValid()) {
        ShortcutItem *scitem = qVariantValue<ShortcutItem *>(current->data(0, Qt::UserRole));
        setKeySequence(scitem->m_cmd->defaultKeySequence());
    }
}

213
void ShortcutSettings::removeTargetIdentifier()
con's avatar
con committed
214
215
{
    m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
216
    targetEdit()->clear();
con's avatar
con committed
217
218
219
220
221
}

void ShortcutSettings::importAction()
{
    QString fileName = QFileDialog::getOpenFileName(0, tr("Import Keyboard Mapping Scheme"),
222
        ICore::instance()->resourcePath() + QLatin1String("/schemes/"),
con's avatar
con committed
223
224
        tr("Keyboard Mapping Scheme (*.kms)"));
    if (!fileName.isEmpty()) {
225

con's avatar
con committed
226
227
228
        CommandsFile cf(fileName);
        QMap<QString, QKeySequence> mapping = cf.importCommands();

hjk's avatar
hjk committed
229
        foreach (ShortcutItem *item, m_scitems) {
230
            QString sid = item->m_cmd->id().toString();
con's avatar
con committed
231
232
233
            if (mapping.contains(sid)) {
                item->m_key = mapping.value(sid);
                item->m_item->setText(2, item->m_key);
234
                if (item->m_item == commandList()->currentItem())
con's avatar
con committed
235
                    commandChanged(item->m_item);
236
237
238
239
240

                if (item->m_cmd->defaultKeySequence() != item->m_key)
                    setModified(item->m_item, true);
                else
                    setModified(item->m_item, false);
con's avatar
con committed
241
242
            }
        }
243
244
245
246
247

        foreach (ShortcutItem *item, m_scitems) {
            resetCollisionMarker(item);
            markPossibleCollisions(item);
        }
con's avatar
con committed
248
249
250
251
252
    }
}

void ShortcutSettings::defaultAction()
{
hjk's avatar
hjk committed
253
    foreach (ShortcutItem *item, m_scitems) {
con's avatar
con committed
254
255
        item->m_key = item->m_cmd->defaultKeySequence();
        item->m_item->setText(2, item->m_key);
256
        setModified(item->m_item, false);
257
        if (item->m_item == commandList()->currentItem())
con's avatar
con committed
258
            commandChanged(item->m_item);
259
260
261
262
263
    }

    foreach (ShortcutItem *item, m_scitems) {
        resetCollisionMarker(item);
        markPossibleCollisions(item);
con's avatar
con committed
264
265
266
267
268
    }
}

void ShortcutSettings::exportAction()
{
hjk's avatar
hjk committed
269
    QString fileName = FileManager::instance()->getSaveFileNameWithExtension(
con's avatar
con committed
270
        tr("Export Keyboard Mapping Scheme"),
271
        ICore::instance()->resourcePath() + QLatin1String("/schemes/"),
272
        tr("Keyboard Mapping Scheme (*.kms)"));
con's avatar
con committed
273
274
275
276
277
278
    if (!fileName.isEmpty()) {
        CommandsFile cf(fileName);
        cf.exportCommands(m_scitems);
    }
}

279
280
281
282
283
284
285
286
287
288
void ShortcutSettings::clear()
{
    QTreeWidget *tree = commandList();
    for (int i = tree->topLevelItemCount()-1; i >= 0 ; --i) {
        delete tree->takeTopLevelItem(i);
    }
    qDeleteAll(m_scitems);
    m_scitems.clear();
}

con's avatar
con committed
289
290
void ShortcutSettings::initialize()
{
291
292
293
    if (!m_initialized)
        return;
    clear();
294
    Core::Internal::ActionManagerPrivate *am = ActionManagerPrivate::instance();
295
296
    QMap<QString, QTreeWidgetItem *> sections;

297
    foreach (Command *c, am->commands()) {
298
        if (c->hasAttribute(Command::CA_NonConfigurable))
con's avatar
con committed
299
300
301
302
303
304
305
            continue;
        if (c->action() && c->action()->isSeparator())
            continue;

        QTreeWidgetItem *item = 0;
        ShortcutItem *s = new ShortcutItem;
        m_scitems << s;
306
        item = new QTreeWidgetItem;
con's avatar
con committed
307
308
309
        s->m_cmd = c;
        s->m_item = item;

310
        const QString identifier = c->id().toString();
311
312
        int pos = identifier.indexOf(QLatin1Char('.'));
        const QString section = identifier.left(pos);
313
        const QString subId = identifier.mid(pos + 1);
314
        if (!sections.contains(section)) {
315
            QTreeWidgetItem *categoryItem = new QTreeWidgetItem(commandList(), QStringList() << section);
316
317
318
319
            QFont f = categoryItem->font(0);
            f.setBold(true);
            categoryItem->setFont(0, f);
            sections.insert(section, categoryItem);
320
            commandList()->expandItem(categoryItem);
321
322
323
        }
        sections[section]->addChild(item);

324
        s->m_key = c->keySequence();
325
        item->setText(0, subId);
326
        item->setText(1, c->description());
con's avatar
con committed
327
        item->setText(2, s->m_key);
328
329
        if (s->m_cmd->defaultKeySequence() != s->m_key)
            setModified(item, true);
330

con's avatar
con committed
331
        item->setData(0, Qt::UserRole, qVariantFromValue(s));
332
333

        markPossibleCollisions(s);
con's avatar
con committed
334
    }
335
    filterChanged(filterText());
con's avatar
con committed
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
}

void ShortcutSettings::handleKeyEvent(QKeyEvent *e)
{
    int nextKey = e->key();
    if ( m_keyNum > 3 ||
         nextKey == Qt::Key_Control ||
         nextKey == Qt::Key_Shift ||
         nextKey == Qt::Key_Meta ||
         nextKey == Qt::Key_Alt )
         return;

    nextKey |= translateModifiers(e->modifiers(), e->text());
    switch (m_keyNum) {
        case 0:
            m_key[0] = nextKey;
            break;
        case 1:
            m_key[1] = nextKey;
            break;
        case 2:
            m_key[2] = nextKey;
            break;
        case 3:
            m_key[3] = nextKey;
            break;
        default:
            break;
    }
    m_keyNum++;
    QKeySequence ks(m_key[0], m_key[1], m_key[2], m_key[3]);
367
    targetEdit()->setText(ks);
con's avatar
con committed
368
369
370
371
372
373
374
375
376
377
378
    e->accept();
}

int ShortcutSettings::translateModifiers(Qt::KeyboardModifiers state,
                                         const QString &text)
{
    int result = 0;
    // The shift modifier only counts when it is not used to type a symbol
    // that is only reachable using the shift key anyway
    if ((state & Qt::ShiftModifier) && (text.size() == 0
                                        || !text.at(0).isPrint()
379
                                        || text.at(0).isLetterOrNumber()
con's avatar
con committed
380
381
382
383
384
385
386
387
388
389
                                        || text.at(0).isSpace()))
        result |= Qt::SHIFT;
    if (state & Qt::ControlModifier)
        result |= Qt::CTRL;
    if (state & Qt::MetaModifier)
        result |= Qt::META;
    if (state & Qt::AltModifier)
        result |= Qt::ALT;
    return result;
}
390
391
392
393
394
395

void ShortcutSettings::markPossibleCollisions(ShortcutItem *item)
{
    if (item->m_key.isEmpty())
        return;

396
397
    int globalId = Context(Constants::C_GLOBAL).at(0);

398
399
400
401
402
403
404
    foreach (ShortcutItem *currentItem, m_scitems) {

        if (currentItem->m_key.isEmpty() || item == currentItem ||
            item->m_key != currentItem->m_key) {
            continue;
        }

hjk's avatar
hjk committed
405
        foreach (int context, currentItem->m_cmd->context()) {
406
407
408
409

            // conflict if context is identical, OR if one
            // of the contexts is the global context
            if (item->m_cmd->context().contains(context) ||
410
               (item->m_cmd->context().contains(globalId) &&
411
                !currentItem->m_cmd->context().isEmpty()) ||
412
                (currentItem->m_cmd->context().contains(globalId) &&
413
414
415
                !item->m_cmd->context().isEmpty())) {
                currentItem->m_item->setForeground(2, Qt::red);
                item->m_item->setForeground(2, Qt::red);
416

417
418
419
420
421
            }
        }
    }
}

422
423
424
425
426
427
void ShortcutSettings::resetCollisionMarker(ShortcutItem *item)
{
    item->m_item->setForeground(2, commandList()->palette().foreground());
}


428
429
430
void ShortcutSettings::resetCollisionMarkers()
{
    foreach (ShortcutItem *item, m_scitems)
431
        resetCollisionMarker(item);
432
}