shortcutsettings.cpp 12.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
hjk's avatar
hjk committed
3
4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
** 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.
23
**
hjk's avatar
hjk committed
24
25
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "shortcutsettings.h"
Eike Ziller's avatar
Eike Ziller committed
31
#include "actionmanager/actionmanager.h"
32
#include "actionmanager/command.h"
con's avatar
con committed
33
#include "command_p.h"
con's avatar
con committed
34
#include "commandsfile.h"
35
#include "coreconstants.h"
36
#include "documentmanager.h"
37
#include "icore.h"
38
#include "id.h"
39

40
41
#include <utils/treewidgetcolumnstretcher.h>

42
43
44
45
46
47
48
49
50
#include <QKeyEvent>
#include <QShortcut>
#include <QHeaderView>
#include <QFileDialog>
#include <QLineEdit>
#include <QAction>
#include <QKeyEvent>
#include <QTreeWidgetItem>
#include <QCoreApplication>
hjk's avatar
hjk committed
51
#include <QDebug>
con's avatar
con committed
52

53
Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*)
con's avatar
con committed
54
55
56
57
58

using namespace Core;
using namespace Core::Internal;

ShortcutSettings::ShortcutSettings(QObject *parent)
59
    : CommandMappings(parent), m_initialized(false)
con's avatar
con committed
60
{
Eike Ziller's avatar
Eike Ziller committed
61
    connect(ActionManager::instance(), SIGNAL(commandListChanged()), this, SLOT(initialize()));
62

hjk's avatar
hjk committed
63
    setId(Core::Constants::SETTINGS_ID_SHORTCUTS);
64
    setDisplayName(tr("Keyboard"));
hjk's avatar
hjk committed
65
    setCategory(Core::Constants::SETTINGS_CATEGORY_CORE);
66
67
    setDisplayCategory(QCoreApplication::translate("Core", Core::Constants::SETTINGS_TR_CATEGORY_CORE));
    setCategoryIcon(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE_ICON));
68
69
}

con's avatar
con committed
70
71
QWidget *ShortcutSettings::createPage(QWidget *parent)
{
72
    m_initialized = true;
con's avatar
con committed
73
74
    m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;

75
    QWidget *w = CommandMappings::createPage(parent);
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

    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
91
92
93
94

    return w;
}

95
void ShortcutSettings::apply()
con's avatar
con committed
96
{
97
98
99
    foreach (ShortcutItem *item, m_scitems)
        item->m_cmd->setKeySequence(item->m_key);
}
con's avatar
con committed
100

101
102
void ShortcutSettings::finish()
{
con's avatar
con committed
103
104
    qDeleteAll(m_scitems);
    m_scitems.clear();
105

106
    CommandMappings::finish();
107
    m_initialized = false;
con's avatar
con committed
108
109
}

110
111
112
113
114
bool ShortcutSettings::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

con's avatar
con committed
115
116
bool ShortcutSettings::eventFilter(QObject *o, QEvent *e)
{
117
118
    Q_UNUSED(o)

con's avatar
con committed
119
120
121
122
123
124
125
    if ( e->type() == QEvent::KeyPress ) {
        QKeyEvent *k = static_cast<QKeyEvent*>(e);
        handleKeyEvent(k);
        return true;
    }

    if ( e->type() == QEvent::Shortcut ||
126
         e->type() == QEvent::KeyRelease ) {
con's avatar
con committed
127
        return true;
128
129
130
131
132
133
134
    }

    if (e->type() == QEvent::ShortcutOverride) {
        // for shortcut overrides, we need to accept as well
        e->accept();
        return true;
    }
con's avatar
con committed
135
136
137
138
139
140

    return false;
}

void ShortcutSettings::commandChanged(QTreeWidgetItem *current)
{
141
142
    CommandMappings::commandChanged(current);
    if (!current || !current->data(0, Qt::UserRole).isValid())
con's avatar
con committed
143
        return;
144
    ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
con's avatar
con committed
145
146
147
    setKeySequence(scitem->m_key);
}

148
void ShortcutSettings::targetIdentifierChanged()
con's avatar
con committed
149
{
150
    QTreeWidgetItem *current = commandList()->currentItem();
con's avatar
con committed
151
    if (current && current->data(0, Qt::UserRole).isValid()) {
152
        ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
con's avatar
con committed
153
        scitem->m_key = QKeySequence(m_key[0], m_key[1], m_key[2], m_key[3]);
154
155
156
157
        if (scitem->m_cmd->defaultKeySequence() != scitem->m_key)
            setModified(current, true);
        else
            setModified(current, false);
158
        current->setText(2, scitem->m_key.toString(QKeySequence::NativeText));
159
        resetCollisionMarker(scitem);
160
        markPossibleCollisions(scitem);
con's avatar
con committed
161
162
163
164
165
166
167
168
169
170
    }
}

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];
    }
171
    targetEdit()->setText(key.toString(QKeySequence::NativeText));
con's avatar
con committed
172
173
}

174
void ShortcutSettings::resetTargetIdentifier()
con's avatar
con committed
175
{
176
    QTreeWidgetItem *current = commandList()->currentItem();
con's avatar
con committed
177
    if (current && current->data(0, Qt::UserRole).isValid()) {
178
        ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
con's avatar
con committed
179
180
181
182
        setKeySequence(scitem->m_cmd->defaultKeySequence());
    }
}

183
void ShortcutSettings::removeTargetIdentifier()
con's avatar
con committed
184
185
{
    m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
186
    targetEdit()->clear();
con's avatar
con committed
187
188
189
190
191
}

void ShortcutSettings::importAction()
{
    QString fileName = QFileDialog::getOpenFileName(0, tr("Import Keyboard Mapping Scheme"),
hjk's avatar
hjk committed
192
        ICore::resourcePath() + QLatin1String("/schemes/"),
con's avatar
con committed
193
194
        tr("Keyboard Mapping Scheme (*.kms)"));
    if (!fileName.isEmpty()) {
195

con's avatar
con committed
196
197
198
        CommandsFile cf(fileName);
        QMap<QString, QKeySequence> mapping = cf.importCommands();

hjk's avatar
hjk committed
199
        foreach (ShortcutItem *item, m_scitems) {
200
            QString sid = item->m_cmd->id().toString();
con's avatar
con committed
201
202
            if (mapping.contains(sid)) {
                item->m_key = mapping.value(sid);
203
                item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
204
                if (item->m_item == commandList()->currentItem())
con's avatar
con committed
205
                    commandChanged(item->m_item);
206
207
208
209
210

                if (item->m_cmd->defaultKeySequence() != item->m_key)
                    setModified(item->m_item, true);
                else
                    setModified(item->m_item, false);
con's avatar
con committed
211
212
            }
        }
213
214
215
216
217

        foreach (ShortcutItem *item, m_scitems) {
            resetCollisionMarker(item);
            markPossibleCollisions(item);
        }
con's avatar
con committed
218
219
220
221
222
    }
}

void ShortcutSettings::defaultAction()
{
hjk's avatar
hjk committed
223
    foreach (ShortcutItem *item, m_scitems) {
con's avatar
con committed
224
        item->m_key = item->m_cmd->defaultKeySequence();
225
        item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
226
        setModified(item->m_item, false);
227
        if (item->m_item == commandList()->currentItem())
con's avatar
con committed
228
            commandChanged(item->m_item);
229
230
231
232
233
    }

    foreach (ShortcutItem *item, m_scitems) {
        resetCollisionMarker(item);
        markPossibleCollisions(item);
con's avatar
con committed
234
235
236
237
238
    }
}

void ShortcutSettings::exportAction()
{
239
    QString fileName = DocumentManager::getSaveFileNameWithExtension(
con's avatar
con committed
240
        tr("Export Keyboard Mapping Scheme"),
hjk's avatar
hjk committed
241
        ICore::resourcePath() + QLatin1String("/schemes/"),
242
        tr("Keyboard Mapping Scheme (*.kms)"));
con's avatar
con committed
243
244
245
246
247
248
    if (!fileName.isEmpty()) {
        CommandsFile cf(fileName);
        cf.exportCommands(m_scitems);
    }
}

249
250
251
252
253
254
255
256
257
258
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
259
260
void ShortcutSettings::initialize()
{
261
262
263
    if (!m_initialized)
        return;
    clear();
264
265
    QMap<QString, QTreeWidgetItem *> sections;

Eike Ziller's avatar
Eike Ziller committed
266
    foreach (Command *c, ActionManager::instance()->commands()) {
267
        if (c->hasAttribute(Command::CA_NonConfigurable))
con's avatar
con committed
268
269
270
271
272
273
274
            continue;
        if (c->action() && c->action()->isSeparator())
            continue;

        QTreeWidgetItem *item = 0;
        ShortcutItem *s = new ShortcutItem;
        m_scitems << s;
275
        item = new QTreeWidgetItem;
con's avatar
con committed
276
277
278
        s->m_cmd = c;
        s->m_item = item;

279
        const QString identifier = c->id().toString();
280
281
        int pos = identifier.indexOf(QLatin1Char('.'));
        const QString section = identifier.left(pos);
282
        const QString subId = identifier.mid(pos + 1);
283
        if (!sections.contains(section)) {
284
            QTreeWidgetItem *categoryItem = new QTreeWidgetItem(commandList(), QStringList() << section);
285
286
287
288
            QFont f = categoryItem->font(0);
            f.setBold(true);
            categoryItem->setFont(0, f);
            sections.insert(section, categoryItem);
289
            commandList()->expandItem(categoryItem);
290
291
292
        }
        sections[section]->addChild(item);

293
        s->m_key = c->keySequence();
294
        item->setText(0, subId);
295
        item->setText(1, c->description());
296
        item->setText(2, s->m_key.toString(QKeySequence::NativeText));
297
298
        if (s->m_cmd->defaultKeySequence() != s->m_key)
            setModified(item, true);
299

con's avatar
con committed
300
        item->setData(0, Qt::UserRole, qVariantFromValue(s));
301
302

        markPossibleCollisions(s);
con's avatar
con committed
303
    }
304
    filterChanged(filterText());
con's avatar
con committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
}

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]);
336
    targetEdit()->setText(ks.toString(QKeySequence::NativeText));
con's avatar
con committed
337
338
339
340
341
342
343
344
345
346
347
    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()
348
                                        || text.at(0).isLetterOrNumber()
con's avatar
con committed
349
350
351
352
353
354
355
356
357
358
                                        || 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;
}
359
360
361
362
363
364

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

365
    Id globalId = Context(Constants::C_GLOBAL).at(0);
366

367
368
369
370
371
372
373
    foreach (ShortcutItem *currentItem, m_scitems) {

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

374
        foreach (Id id, currentItem->m_cmd->context()) {
375
376
            // conflict if context is identical, OR if one
            // of the contexts is the global context
377
            if (item->m_cmd->context().contains(id) ||
378
               (item->m_cmd->context().contains(globalId) &&
379
                !currentItem->m_cmd->context().isEmpty()) ||
380
                (currentItem->m_cmd->context().contains(globalId) &&
381
382
383
                !item->m_cmd->context().isEmpty())) {
                currentItem->m_item->setForeground(2, Qt::red);
                item->m_item->setForeground(2, Qt::red);
384

385
386
387
388
389
            }
        }
    }
}

390
391
392
393
394
395
void ShortcutSettings::resetCollisionMarker(ShortcutItem *item)
{
    item->m_item->setForeground(2, commandList()->palette().foreground());
}


396
397
398
void ShortcutSettings::resetCollisionMarkers()
{
    foreach (ShortcutItem *item, m_scitems)
399
        resetCollisionMarker(item);
400
}