• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 6685397774

29 Oct 2023 07:37PM UTC coverage: 11.735% (-0.002%) from 11.737%
6685397774

push

github

10110111
Deduplicate title bar implementation

131 of 131 new or added lines in 56 files covered. (100.0%)

14842 of 126472 relevant lines covered (11.74%)

21617.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/gui/ShortcutsDialog.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2012 Anton Samoylov
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18
 */
19

20
#include <QDialog>
21
#include <QStandardItemModel>
22
#include <QDebug>
23

24
#include "StelApp.hpp"
25
#include "StelGui.hpp"
26
#include "StelTranslator.hpp"
27
#include "StelActionMgr.hpp"
28
#include "ShortcutLineEdit.hpp"
29
#include "ShortcutsDialog.hpp"
30
#include "ui_shortcutsDialog.h"
31

32

33
ShortcutsFilterModel::ShortcutsFilterModel(QObject* parent) :
×
34
    QSortFilterProxyModel(parent)
×
35
{
36
        //
37
}
×
38

39
bool ShortcutsFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
×
40
{
41
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
42
        if (filterRegularExpression().pattern().isEmpty())
43
#else
44
        if (filterRegExp().pattern().isEmpty())
×
45
#endif
46
                return true;
×
47
        
48
        if (source_parent.isValid())
×
49
        {
50
                QModelIndex index = source_parent.model()->index(source_row, filterKeyColumn(), source_parent);
×
51
                QString data = sourceModel()->data(index, filterRole()).toString();
×
52
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
53
                return data.contains(filterRegularExpression());
54
#else
55
                return data.contains(filterRegExp());
×
56
#endif
57
        }
×
58
        else
59
        {
60
                QModelIndex index = sourceModel()->index(source_row, filterKeyColumn());
×
61
                for (int row = 0; row < sourceModel()->rowCount(index); row++)
×
62
                {
63
                        if (filterAcceptsRow(row, index))
×
64
                                return true;
×
65
                }
66
        }
67
        return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
×
68
}
69

70

71
ShortcutsDialog::ShortcutsDialog(QObject* parent) :
×
72
        StelDialog("Shortcuts", parent),
73
        ui(new Ui_shortcutsDialogForm),
×
74
        filterModel(new ShortcutsFilterModel(this)),
×
75
        mainModel(new QStandardItemModel(this))
×
76
{
77
        actionMgr = StelApp::getInstance().getStelActionManager();
×
78
}
×
79

80
ShortcutsDialog::~ShortcutsDialog()
×
81
{
82
        collisionItems.clear();
×
83
        delete ui;
×
84
        ui = nullptr;
×
85
}
×
86

87
void ShortcutsDialog::drawCollisions()
×
88
{
89
        QBrush brush(Qt::red);
×
90
        for (auto* item : qAsConst(collisionItems))
×
91
        {
92
                // change colors of all columns for better visibility
93
                item->setForeground(brush);
×
94
                QModelIndex index = item->index();
×
95
                mainModel->itemFromIndex(index.sibling(index.row(), 1))->setForeground(brush);
×
96
                mainModel->itemFromIndex(index.sibling(index.row(), 2))->setForeground(brush);
×
97
        }
98
}
×
99

100
void ShortcutsDialog::resetCollisions()
×
101
{
102
        QBrush brush =
103
#if (QT_VERSION>=QT_VERSION_CHECK(5,15,0))
104
                ui->shortcutsTreeView->palette().windowText();
×
105
#else
106
                ui->shortcutsTreeView->palette().brush(QPalette::Foreground);
107
#endif
108
        for (auto* item : qAsConst(collisionItems))
×
109
        {
110
                item->setForeground(brush);
×
111
                QModelIndex index = item->index();
×
112
                mainModel->itemFromIndex(index.sibling(index.row(), 1))->setForeground(brush);
×
113
                mainModel->itemFromIndex(index.sibling(index.row(), 2))->setForeground(brush);
×
114
        }
115
        collisionItems.clear();
×
116
}
×
117

118
void ShortcutsDialog::retranslate()
×
119
{
120
        if (dialog)
×
121
        {
122
                ui->retranslateUi(dialog);
×
123
                setModelHeader();
×
124
                updateTreeData();
×
125
        }
126
}
×
127

128
void ShortcutsDialog::initEditors()
×
129
{
130
        QModelIndex index = filterModel->mapToSource(ui->shortcutsTreeView->currentIndex());
×
131
        index = index.sibling(index.row(), 0);
×
132
        QStandardItem* currentItem = mainModel->itemFromIndex(index);
×
133
        if (itemIsEditable(currentItem))
×
134
        {
135
                // current item is shortcut, not group (group items aren't selectable)
136
                ui->primaryShortcutEdit->setEnabled(true);
×
137
                ui->altShortcutEdit->setEnabled(true);
×
138
                ui->restoreDefaultsButton->setEnabled(true);
×
139
                // fill editors with item's shortcuts
140
                QVariant data = mainModel->data(index.sibling(index.row(), 1));
×
141
                ui->primaryShortcutEdit->setContents(data.value<QKeySequence>());
×
142
                data = mainModel->data(index.sibling(index.row(), 2));
×
143
                ui->altShortcutEdit->setContents(data.value<QKeySequence>());
×
144
        }
×
145
        else
146
        {
147
                // item is group, not shortcut
148
                ui->primaryShortcutEdit->setEnabled(false);
×
149
                ui->altShortcutEdit->setEnabled(false);
×
150
                ui->applyButton->setEnabled(false);
×
151
                ui->restoreDefaultsButton->setEnabled(false);
×
152
                // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
153
                ui->primaryShortcutEdit->setText("");
×
154
                ui->altShortcutEdit->setText("");
×
155
        }
156
        polish();
×
157
}
×
158

159
bool ShortcutsDialog::prefixMatchKeySequence(const QKeySequence& ks1,
×
160
                                             const QKeySequence& ks2)
161
{
162
        if (ks1.isEmpty() || ks2.isEmpty())
×
163
        {
164
                return false;
×
165
        }
166
        for (uint i = 0; i < static_cast<uint>(qMin(ks1.count(), ks2.count())); ++i)
×
167
        {
168
                if (ks1[i] != ks2[i])
×
169
                {
170
                        return false;
×
171
                }
172
        }
173
        return true;
×
174
}
175

176
QList<QStandardItem*> ShortcutsDialog::findCollidingItems(QKeySequence ks)
×
177
{
178
        QList<QStandardItem*> result;
×
179
        for (int row = 0; row < mainModel->rowCount(); row++)
×
180
        {
181
                QStandardItem* group = mainModel->item(row, 0);
×
182
                if (!group->hasChildren())
×
183
                        continue;
×
184
                for (int subrow = 0; subrow < group->rowCount(); subrow++)
×
185
                {
186
                        QKeySequence primary(group->child(subrow, 1)
×
187
                                             ->data(Qt::DisplayRole).toString());
×
188
                        QKeySequence secondary(group->child(subrow, 2)
×
189
                                               ->data(Qt::DisplayRole).toString());
×
190
                        if (prefixMatchKeySequence(ks, primary) ||
×
191
                            prefixMatchKeySequence(ks, secondary))
×
192
                                result.append(group->child(subrow, 0));
×
193
                }
×
194
        }
195
        return result;
×
196
}
×
197

198
void ShortcutsDialog::handleCollisions(ShortcutLineEdit *currentEdit)
×
199
{
200
        resetCollisions();
×
201
        
202
        // handle collisions
203
        QString text = currentEdit->text();
×
204
        collisionItems = findCollidingItems(QKeySequence(text));
×
205
        QModelIndex index =
206
                filterModel->mapToSource(ui->shortcutsTreeView->currentIndex());
×
207
        index = index.sibling(index.row(), 0);
×
208
        QStandardItem* currentItem = mainModel->itemFromIndex(index);
×
209
        collisionItems.removeOne(currentItem);
×
210
        if (!collisionItems.isEmpty())
×
211
        {
212
                drawCollisions();
×
213
                ui->applyButton->setEnabled(false);                
×
214
                // scrolling to first collision item
215
                QModelIndex first = filterModel->mapFromSource(collisionItems.first()->index());
×
216
                ui->shortcutsTreeView->scrollTo(first);
×
217
                currentEdit->setProperty("collision", true);
×
218
        }
219
        else
220
        {
221
                // scrolling back to current item
222
                QModelIndex current = filterModel->mapFromSource(index);
×
223
                ui->shortcutsTreeView->scrollTo(current);
×
224
                currentEdit->setProperty("collision", false);
×
225
        }
226
}
×
227

228
void ShortcutsDialog::handleChanges()
×
229
{
230
        // work only with changed editor
231
        ShortcutLineEdit* editor = qobject_cast<ShortcutLineEdit*>(sender());
×
232
        bool isPrimary = (editor == ui->primaryShortcutEdit);
×
233
        // updating clear buttons
234
        if (isPrimary)
×
235
        {
236
                ui->primaryBackspaceButton->setEnabled(!editor->isEmpty());
×
237
        }
238
        else
239
        {
240
                ui->altBackspaceButton->setEnabled(!editor->isEmpty());
×
241
        }
242
        // updating apply button
243
        QModelIndex index = filterModel->mapToSource(ui->shortcutsTreeView->currentIndex());
×
244
        if (!index.isValid() ||
×
245
            (isPrimary && editor->text() == mainModel->data(index.sibling(index.row(), 1))) ||
×
246
            (!isPrimary && editor->text() == mainModel->data(index.sibling(index.row(), 2))))
×
247
        {
248
                // nothing to apply
249
                ui->applyButton->setEnabled(false);                
×
250
        }
251
        else
252
        {
253
                ui->applyButton->setEnabled(true);                
×
254
        }
255
        handleCollisions(editor);
×
256
        polish();
×
257
}
×
258

259
void ShortcutsDialog::applyChanges()
×
260
{
261
        // get ids stored in tree
262
        QModelIndex index = filterModel->mapToSource(ui->shortcutsTreeView->currentIndex());
×
263
        if (!index.isValid())
×
264
                return;
×
265
        index = index.sibling(index.row(), 0);
×
266
        QStandardItem* currentItem = mainModel->itemFromIndex(index);
×
267
        QString actionId = currentItem->data(Qt::UserRole).toString();
×
268

269
        StelAction* action = actionMgr->findAction(actionId);
×
270
        action->setShortcut(ui->primaryShortcutEdit->getKeySequence().toString());
×
271
        action->setAltShortcut(ui->altShortcutEdit->getKeySequence().toString());
×
272
        updateShortcutsItem(action);
×
273

274
        // save shortcuts to file
275
        actionMgr->saveShortcuts();
×
276

277
        // nothing to apply until edits' content changes
278
        ui->applyButton->setEnabled(false);
×
279
        ui->restoreDefaultsButton->setEnabled(true);
×
280
}
×
281

282
void ShortcutsDialog::switchToEditors(const QModelIndex& index)
×
283
{
284
        QModelIndex mainIndex = filterModel->mapToSource(index);
×
285
        QStandardItem* item = mainModel->itemFromIndex(mainIndex);
×
286
        if (itemIsEditable(item))
×
287
        {
288
                ui->primaryShortcutEdit->setFocus();
×
289
        }
290
}
×
291

292
void ShortcutsDialog::createDialogContent()
×
293
{
294
        ui->setupUi(dialog);
×
295
        connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
×
296

297
        resetModel();
×
298
        filterModel->setSourceModel(mainModel);
×
299
        filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
×
300
        filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
×
301
        filterModel->setDynamicSortFilter(true);
×
302
        filterModel->setSortLocaleAware(true);
×
303
        ui->shortcutsTreeView->setModel(filterModel);
×
304
        ui->shortcutsTreeView->header()->setSectionsMovable(false);
×
305
        ui->shortcutsTreeView->sortByColumn(0, Qt::AscendingOrder);
×
306

307
        // Kinetic scrolling
308
        kineticScrollingList << ui->shortcutsTreeView;
×
309
        StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
×
310
        if (gui)
×
311
        {
312
                enableKineticScrolling(gui->getFlagUseKineticScrolling());
×
313
                connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
×
314
        }
315

316
        connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
×
317
        connect(ui->shortcutsTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
×
318
                this, SLOT(initEditors()));
319
        connect(ui->shortcutsTreeView, SIGNAL(activated(QModelIndex)),
×
320
                this, SLOT(switchToEditors(QModelIndex)));
321
        connect(ui->lineEditSearch, SIGNAL(textChanged(QString)),
×
322
                filterModel, SLOT(setFilterFixedString(QString)));
×
323
        
324
        // apply button logic
325
        connect(ui->applyButton, SIGNAL(clicked()), this, SLOT(applyChanges()));
×
326
        // restore defaults button logic
327
        connect(ui->restoreDefaultsButton, SIGNAL(clicked()), this, SLOT(restoreDefaultShortcuts()));
×
328
        connect(ui->restoreAllDefaultsButton, SIGNAL(clicked()), this, SLOT(restoreAllDefaultShortcuts()));
×
329
        // we need to disable all shortcut actions, so we can enter shortcuts without activating any actions
330
        connect(ui->primaryShortcutEdit, SIGNAL(focusChanged(bool)), actionMgr, SLOT(setAllActionsEnabled(bool)));
×
331
        connect(ui->altShortcutEdit, SIGNAL(focusChanged(bool)), actionMgr, SLOT(setAllActionsEnabled(bool)));
×
332
        // handling changes in editors
333
        connect(ui->primaryShortcutEdit, SIGNAL(contentsChanged()), this, SLOT(handleChanges()));
×
334
        connect(ui->altShortcutEdit, SIGNAL(contentsChanged()), this, SLOT(handleChanges()));
×
335

336
        QString backspaceChar;
×
337
        backspaceChar.append(QChar(0x232B)); // Erase left
×
338
        //test.append(QChar(0x2672));
339
        //test.append(QChar(0x267B));
340
        //test.append(QChar(0x267C));
341
        //test.append(QChar(0x21BA)); // Counter-clockwise
342
        //test.append(QChar(0x2221)); // Angle sign
343

344
        updateTreeData();
×
345

346
        // set initial focus to action search
347
        ui->lineEditSearch->setFocus();
×
348
}
×
349

350
void ShortcutsDialog::polish()
×
351
{
352
        ui->primaryShortcutEdit->style()->unpolish(ui->primaryShortcutEdit);
×
353
        ui->primaryShortcutEdit->style()->polish(ui->primaryShortcutEdit);
×
354
        ui->altShortcutEdit->style()->unpolish(ui->altShortcutEdit);
×
355
        ui->altShortcutEdit->style()->polish(ui->altShortcutEdit);
×
356
}
×
357

358
QStandardItem* ShortcutsDialog::updateGroup(const QString& group)
×
359
{
360
        QStandardItem* groupItem = findItemByData(QVariant(group),
×
361
                                                  Qt::UserRole);
362
        bool isNew = false;
×
363
        if (!groupItem)
×
364
        {
365
                // create new
366
                groupItem = new QStandardItem();
×
367
                isNew = true;
×
368
        }
369
        // group items aren't selectable, so reset default flag
370
        groupItem->setFlags(Qt::ItemIsEnabled);
×
371
        
372
        // setup displayed text
373
        groupItem->setText(q_(group));
×
374
        // store id
375
        groupItem->setData(group, Qt::UserRole);
×
376
        groupItem->setColumnCount(3);
×
377
        // setup bold font for group lines
378
        QFont rootFont = groupItem->font();
×
379
        rootFont.setBold(true);
×
380
        // Font size is 14
381
        rootFont.setPixelSize(StelApp::getInstance().getScreenFontSize()+1);
×
382
        groupItem->setFont(rootFont);
×
383
        if (isNew)
×
384
                mainModel->appendRow(groupItem);
×
385
        
386

387
        QModelIndex index = filterModel->mapFromSource(groupItem->index());
×
388
        ui->shortcutsTreeView->expand(index);
×
389
        ui->shortcutsTreeView->setFirstColumnSpanned(index.row(), QModelIndex(), true);
×
390
        ui->shortcutsTreeView->setRowHidden(index.row(), QModelIndex(), false);
×
391
        
392
        return groupItem;
×
393
}
×
394

395
QStandardItem* ShortcutsDialog::findItemByData(QVariant value, int role, int column) const
×
396
{
397
        for (int row = 0; row < mainModel->rowCount(); row++)
×
398
        {
399
                QStandardItem* item = mainModel->item(row, 0);
×
400
                if (!item)
×
401
                        continue; //WTF?
×
402
                if (column == 0)
×
403
                {
404
                        if (item->data(role) == value)
×
405
                                return item;
×
406
                }
407
                
408
                for (int subrow = 0; subrow < item->rowCount(); subrow++)
×
409
                {
410
                        QStandardItem* subitem = item->child(subrow, column);
×
411
                        if (subitem->data(role) == value)
×
412
                                return subitem;
×
413
                }
414
        }
415
        return nullptr;
×
416
}
417

418
void ShortcutsDialog::updateShortcutsItem(StelAction *action,
×
419
                                          QStandardItem *shortcutItem)
420
{
421
        QVariant shortcutId(action->getId());
×
422
        if (shortcutItem == nullptr)
×
423
        {
424
                // search for item
425
                shortcutItem = findItemByData(shortcutId, Qt::UserRole, 0);
×
426
        }
427
        // we didn't find item, create and add new
428
        QStandardItem* groupItem = nullptr;
×
429
        if (shortcutItem == nullptr)
×
430
        {
431
                // firstly search for group
432
                QVariant groupId(action->getGroup());
×
433
                groupItem = findItemByData(groupId, Qt::UserRole, 0);
×
434
                if (groupItem == nullptr)
×
435
                {
436
                        // create and add new group to treeWidget
437
                        groupItem = updateGroup(action->getGroup());
×
438
                }
439
                // create shortcut item
440
                shortcutItem = new QStandardItem();
×
441
                shortcutItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
×
442
                groupItem->appendRow(shortcutItem);
×
443
                // store shortcut id, so we can find it when shortcut changed
444
                shortcutItem->setData(shortcutId, Qt::UserRole);
×
445
                QStandardItem* primaryItem = new QStandardItem();
×
446
                QStandardItem* secondaryItem = new QStandardItem();
×
447
                primaryItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
×
448
                secondaryItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
×
449
                groupItem->setChild(shortcutItem->row(), 1, primaryItem);
×
450
                groupItem->setChild(shortcutItem->row(), 2, secondaryItem);
×
451
        }
×
452
        // setup properties of item
453
        shortcutItem->setText(action->getText());
×
454
        QModelIndex index = shortcutItem->index();
×
455
        mainModel->setData(index.sibling(index.row(), 1),
×
456
                           action->getShortcut(), Qt::DisplayRole);
×
457
        mainModel->setData(index.sibling(index.row(), 2),
×
458
                           action->getAltShortcut(), Qt::DisplayRole);
×
459
}
×
460

461
void ShortcutsDialog::restoreAllDefaultShortcuts()
×
462
{
463
        if (askConfirmation())
×
464
        {
465
                qDebug() << "[Shortcuts] restore defaults...";
×
466
                resetModel();
×
467
                actionMgr->restoreDefaultShortcuts();
×
468
                updateTreeData();
×
469
                initEditors();
×
470
        }
471
        else
472
                qDebug() << "[Shortcuts] restore defaults is canceled...";
×
473
}
×
474

475
void ShortcutsDialog::restoreDefaultShortcuts()
×
476
{
477
        // get ids stored in tree
478
        QModelIndex index = filterModel->mapToSource(ui->shortcutsTreeView->currentIndex());
×
479
        if (!index.isValid())
×
480
                return;
×
481
        index = index.sibling(index.row(), 0);
×
482
        QStandardItem* currentItem = mainModel->itemFromIndex(index);
×
483
        QString actionId = currentItem->data(Qt::UserRole).toString();
×
484

485
        StelAction* action = actionMgr->findAction(actionId);
×
486
        if (action)
×
487
        {
488
                actionMgr->restoreDefaultShortcut(action);
×
489
                updateShortcutsItem(action);
×
490
                ui->primaryShortcutEdit->setText(action->getShortcut().toString());
×
491
                ui->altShortcutEdit->setText(action->getAltShortcut().toString());
×
492
                // nothing to apply until edits' content changes
493
                ui->applyButton->setEnabled(false);
×
494
                ui->restoreDefaultsButton->setEnabled(false);
×
495
        }
496
}
×
497

498
void ShortcutsDialog::updateTreeData()
×
499
{
500
        // Create shortcuts tree
501
        const QStringList groups = actionMgr->getGroupList();
×
502
        for (const auto& group : groups)
×
503
        {
504
                updateGroup(group);
×
505
                // display group's shortcuts
506
                const QList<StelAction*> actions = actionMgr->getActionList(group);
×
507
                for (auto* action : actions)
×
508
                {
509
                        updateShortcutsItem(action);
×
510
                }
511
        }
×
512
        // adjust columns
513
        for(int i=0; i<3; i++)
×
514
                ui->shortcutsTreeView->resizeColumnToContents(i);
×
515
}
×
516

517
bool ShortcutsDialog::itemIsEditable(QStandardItem *item)
×
518
{
519
        if (item == nullptr) return false;
×
520
        // non-editable items(not group items) have no Qt::ItemIsSelectable flag
521
        return (Qt::ItemIsSelectable & item->flags());
×
522
}
523

524
void ShortcutsDialog::resetModel()
×
525
{
526
        mainModel->clear();
×
527
        setModelHeader();
×
528
}
×
529

530
void ShortcutsDialog::setModelHeader()
×
531
{
532
        QStringList headerLabels;
×
533
        headerLabels << q_("Action") << qc_("Primary shortcut","column name") << qc_("Alternative shortcut","column name");
×
534
        mainModel->setHorizontalHeaderLabels(headerLabels);
×
535
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc