keepassxc

Форк
0
/
EntryModel.cpp 
622 строки · 18.8 Кб
1
/*
2
 *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "EntryModel.h"
19

20
#include <QFont>
21
#include <QMimeData>
22
#include <QPalette>
23

24
#include "core/Entry.h"
25
#include "core/Group.h"
26
#include "core/Metadata.h"
27
#include "core/PasswordHealth.h"
28
#include "gui/DatabaseIcons.h"
29
#include "gui/Icons.h"
30
#include "gui/styles/StateColorPalette.h"
31
#ifdef Q_OS_MACOS
32
#include "gui/osutils/macutils/MacUtils.h"
33
#endif
34

35
EntryModel::EntryModel(QObject* parent)
36
    : QAbstractTableModel(parent)
37
    , m_group(nullptr)
38
    , HiddenContentDisplay(QString("\u25cf").repeated(6))
39
    , DateFormat(Qt::DefaultLocaleShortDate)
40
{
41
    connect(config(), &Config::changed, this, &EntryModel::onConfigChanged);
42
}
43

44
Entry* EntryModel::entryFromIndex(const QModelIndex& index) const
45
{
46
    Q_ASSERT(index.isValid() && index.row() < m_entries.size());
47
    return m_entries.at(index.row());
48
}
49

50
QModelIndex EntryModel::indexFromEntry(Entry* entry) const
51
{
52
    int row = m_entries.indexOf(entry);
53
    if (row >= 0) {
54
        return index(row, 1);
55
    }
56
    return {};
57
}
58

59
void EntryModel::setGroup(Group* group)
60
{
61
    if (!group || group == m_group) {
62
        return;
63
    }
64

65
    beginResetModel();
66

67
    severConnections();
68

69
    m_group = group;
70
    m_allGroups.clear();
71
    m_entries = group->entries();
72
    m_orgEntries.clear();
73

74
    makeConnections(group);
75

76
    endResetModel();
77
}
78

79
void EntryModel::setEntries(const QList<Entry*>& entries)
80
{
81
    beginResetModel();
82

83
    severConnections();
84

85
    m_group = nullptr;
86
    m_allGroups.clear();
87
    m_entries = entries;
88
    m_orgEntries = entries;
89

90
    for (const auto entry : asConst(m_entries)) {
91
        if (entry->group()) {
92
            m_allGroups.insert(entry->group());
93
        }
94
    }
95

96
    for (const auto group : m_allGroups) {
97
        makeConnections(group);
98
    }
99

100
    endResetModel();
101
}
102

103
int EntryModel::rowCount(const QModelIndex& parent) const
104
{
105
    if (parent.isValid()) {
106
        return 0;
107
    } else {
108
        return m_entries.size();
109
    }
110
}
111

112
int EntryModel::columnCount(const QModelIndex& parent) const
113
{
114
    // Advised by Qt documentation
115
    if (parent.isValid()) {
116
        return 0;
117
    }
118

119
    return 16;
120
}
121

122
QVariant EntryModel::data(const QModelIndex& index, int role) const
123
{
124
    if (!index.isValid()) {
125
        return {};
126
    }
127

128
    Entry* entry = entryFromIndex(index);
129
    EntryAttributes* attr = entry->attributes();
130

131
    if (role == Qt::DisplayRole) {
132
        QString result;
133
        switch (index.column()) {
134
        case ParentGroup:
135
            if (entry->group()) {
136
                return entry->group()->name();
137
            }
138
            break;
139
        case Title:
140
            result = entry->resolveMultiplePlaceholders(entry->title());
141
            if (attr->isReference(EntryAttributes::TitleKey)) {
142
                result.prepend(tr("Ref: ", "Reference abbreviation"));
143
            }
144
            return result;
145
        case Username:
146
            if (config()->get(Config::GUI_HideUsernames).toBool()) {
147
                result = EntryModel::HiddenContentDisplay;
148
            } else {
149
                result = entry->resolveMultiplePlaceholders(entry->username());
150
            }
151
            if (attr->isReference(EntryAttributes::UserNameKey)) {
152
                result.prepend(tr("Ref: ", "Reference abbreviation"));
153
            }
154
            if (entry->username().isEmpty() && !config()->get(Config::Security_PasswordEmptyPlaceholder).toBool()) {
155
                result = "";
156
            }
157
            return result;
158
        case Password:
159
            if (config()->get(Config::GUI_HidePasswords).toBool()) {
160
                result = EntryModel::HiddenContentDisplay;
161
            } else {
162
                result = entry->resolveMultiplePlaceholders(entry->password());
163
            }
164
            if (attr->isReference(EntryAttributes::PasswordKey)) {
165
                result.prepend(tr("Ref: ", "Reference abbreviation"));
166
            }
167
            if (entry->password().isEmpty() && !config()->get(Config::Security_PasswordEmptyPlaceholder).toBool()) {
168
                result = "";
169
            }
170
            return result;
171
        case Url:
172
            result = entry->resolveMultiplePlaceholders(entry->displayUrl());
173
            if (attr->isReference(EntryAttributes::URLKey)) {
174
                result.prepend(tr("Ref: ", "Reference abbreviation"));
175
            }
176
            return result;
177
        case Notes:
178
            if (!entry->notes().isEmpty()) {
179
                if (config()->get(Config::Security_HideNotes).toBool()) {
180
                    result = EntryModel::HiddenContentDisplay;
181
                } else {
182
                    // Display only first line of notes in simplified format if not hidden
183
                    result = entry->notes().section("\n", 0, 0).simplified();
184
                }
185
                if (attr->isReference(EntryAttributes::NotesKey)) {
186
                    result.prepend(tr("Ref: ", "Reference abbreviation"));
187
                }
188
            }
189
            return result;
190
        case Expires:
191
            // Display either date of expiry or 'Never'
192
            result = entry->timeInfo().expires()
193
                         ? entry->timeInfo().expiryTime().toLocalTime().toString(EntryModel::DateFormat)
194
                         : tr("Never");
195
            return result;
196
        case Created:
197
            result = entry->timeInfo().creationTime().toLocalTime().toString(EntryModel::DateFormat);
198
            return result;
199
        case Modified:
200
            result = entry->timeInfo().lastModificationTime().toLocalTime().toString(EntryModel::DateFormat);
201
            return result;
202
        case Accessed:
203
            result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat);
204
            return result;
205
        case Attachments: {
206
            // Display comma-separated list of attachments
207
            QList<QString> attachments = entry->attachments()->keys();
208
            for (const auto& attachment : attachments) {
209
                if (result.isEmpty()) {
210
                    result.append(attachment);
211
                    continue;
212
                }
213
                result.append(QString(", ") + attachment);
214
            }
215
            return result;
216
        }
217
        case Size: {
218
            const int unitsSize = 4;
219
            QString units[unitsSize] = {"B", "KiB", "MiB", "GiB"};
220
            float resultInt = entry->size();
221

222
            for (int i = 0; i < unitsSize; i++) {
223
                if (resultInt < 1024 || i == unitsSize - 1) {
224
                    resultInt = qRound(resultInt * 100) / 100.0;
225
                    result = QStringLiteral("%1 %2").arg(QString::number(resultInt), units[i]);
226
                    break;
227
                }
228
                resultInt /= 1024.0;
229
            }
230

231
            return result;
232
        }
233
        case Color:
234
            QColor backgroundColor;
235
            backgroundColor.setNamedColor(entry->backgroundColor());
236
            if (backgroundColor.isValid()) {
237
                result = "▍";
238
                return result;
239
            }
240
        }
241
    } else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView()
242
        switch (index.column()) {
243
        case Username:
244
            return entry->resolveMultiplePlaceholders(entry->username());
245
        case Password:
246
            return entry->resolveMultiplePlaceholders(entry->password());
247
        case PasswordStrength: {
248
            if (!entry->password().isEmpty() && !entry->excludeFromReports()) {
249
                return entry->passwordHealth()->score();
250
            }
251
            return 0;
252
        }
253
        case Expires:
254
            // There seems to be no better way of expressing 'infinity'
255
            return entry->timeInfo().expires() ? entry->timeInfo().expiryTime() : QDateTime(QDate(9999, 1, 1));
256
        case Created:
257
            return entry->timeInfo().creationTime();
258
        case Modified:
259
            return entry->timeInfo().lastModificationTime();
260
        case Accessed:
261
            return entry->timeInfo().lastAccessTime();
262
        case Paperclip:
263
            // Display entries with attachments above those without when
264
            // sorting ascendingly (and vice versa when sorting descendingly)
265
            return !entry->attachments()->isEmpty();
266
        case Totp:
267
            return entry->hasTotp();
268
        case Size:
269
            return entry->size();
270
        default:
271
            // For all other columns, simply use data provided by Qt::Display-
272
            // Role for sorting
273
            return data(index, Qt::DisplayRole);
274
        }
275
    } else if (role == Qt::DecorationRole) {
276
        switch (index.column()) {
277
        case ParentGroup:
278
            if (entry->group()) {
279
                return Icons::groupIconPixmap(entry->group());
280
            }
281
            break;
282
        case Title:
283
            return Icons::entryIconPixmap(entry);
284
        case Paperclip:
285
            if (!entry->attachments()->isEmpty()) {
286
                return icons()->icon("paperclip");
287
            }
288
            break;
289
        case Totp:
290
            if (entry->hasTotp()) {
291
                return icons()->icon("totp");
292
            }
293
            break;
294
        case PasswordStrength:
295
            if (!entry->password().isEmpty() && !entry->excludeFromReports()) {
296
                StateColorPalette statePalette;
297
                QColor color = statePalette.color(StateColorPalette::Error);
298

299
                switch (entry->passwordHealth()->quality()) {
300
                case PasswordHealth::Quality::Bad:
301
                case PasswordHealth::Quality::Poor:
302
                    color = statePalette.color(StateColorPalette::HealthCritical);
303
                    break;
304
                case PasswordHealth::Quality::Weak:
305
                    color = statePalette.color(StateColorPalette::HealthBad);
306
                    break;
307
                case PasswordHealth::Quality::Good:
308
                case PasswordHealth::Quality::Excellent:
309
                    color = statePalette.color(StateColorPalette::HealthExcellent);
310
                    break;
311
                }
312

313
                return color;
314
            }
315
            break;
316
        }
317
    } else if (role == Qt::FontRole) {
318
        QFont font;
319
        if (entry->isExpired()) {
320
            font.setStrikeOut(true);
321
        }
322
        return font;
323
    } else if (role == Qt::ForegroundRole) {
324

325
        if (index.column() == Color) {
326
            QColor backgroundColor;
327
            backgroundColor.setNamedColor(entry->backgroundColor());
328
            if (backgroundColor.isValid()) {
329
                return backgroundColor;
330
            }
331
        }
332

333
        QColor foregroundColor;
334
        foregroundColor.setNamedColor(entry->foregroundColor());
335
        if (entry->hasReferences()) {
336
            QPalette p;
337
            foregroundColor = p.color(QPalette::Current, QPalette::Text);
338
            int lightness =
339
                qMin(255, qMax(0, foregroundColor.lightness() + (foregroundColor.lightness() < 110 ? 85 : -51)));
340
            foregroundColor.setHsl(foregroundColor.hue(), foregroundColor.saturation(), lightness);
341
            return QVariant(foregroundColor);
342
        } else if (foregroundColor.isValid()) {
343
            return QVariant(foregroundColor);
344
        }
345
    } else if (role == Qt::BackgroundRole) {
346
        if (m_backgroundColorVisible) {
347
            QColor backgroundColor;
348
            backgroundColor.setNamedColor(entry->backgroundColor());
349
            if (backgroundColor.isValid()) {
350
                return QVariant(backgroundColor);
351
            }
352
        }
353
    } else if (role == Qt::ToolTipRole) {
354
        if (index.column() == PasswordStrength && !entry->password().isEmpty() && !entry->excludeFromReports()) {
355
            return entry->passwordHealth()->scoreReason();
356
        }
357
    }
358

359
    return {};
360
}
361

362
QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const
363
{
364
    Q_UNUSED(orientation);
365

366
    if (role == Qt::DisplayRole) {
367
        switch (section) {
368
        case ParentGroup:
369
            return tr("Group");
370
        case Title:
371
            return tr("Title");
372
        case Username:
373
            return tr("Username");
374
        case Password:
375
            return tr("Password");
376
        case Url:
377
            return tr("URL");
378
        case Notes:
379
            return tr("Notes");
380
        case Expires:
381
            return tr("Expires");
382
        case Created:
383
            return tr("Created");
384
        case Modified:
385
            return tr("Modified");
386
        case Accessed:
387
            return tr("Accessed");
388
        case Attachments:
389
            return tr("Attachments");
390
        case Size:
391
            return tr("Size");
392
        }
393

394
    } else if (role == Qt::DecorationRole) {
395
        switch (section) {
396
        case Paperclip:
397
            return icons()->icon("paperclip");
398
        case Totp:
399
            return icons()->icon("totp");
400
        case PasswordStrength:
401
            return icons()->icon("lock-question");
402
        }
403
    } else if (role == Qt::ToolTipRole) {
404
        switch (section) {
405
        case ParentGroup:
406
            return tr("Group name");
407
        case Title:
408
            return tr("Entry title");
409
        case Username:
410
            return tr("Username");
411
        case Password:
412
            return tr("Password");
413
        case PasswordStrength:
414
            return tr("Password Strength");
415
        case Url:
416
            return tr("URL");
417
        case Notes:
418
            return tr("Entry notes");
419
        case Expires:
420
            return tr("Entry expires at");
421
        case Created:
422
            return tr("Creation date");
423
        case Modified:
424
            return tr("Last modification date");
425
        case Accessed:
426
            return tr("Last access date");
427
        case Attachments:
428
            return tr("Attached files");
429
        case Size:
430
            return tr("Entry size");
431
        case Paperclip:
432
            return tr("Has attachments");
433
        case Totp:
434
            return tr("Has TOTP");
435
        case Color:
436
            return tr("Background Color");
437
        }
438
    }
439

440
    return {};
441
}
442

443
Qt::DropActions EntryModel::supportedDropActions() const
444
{
445
    return Qt::IgnoreAction;
446
}
447

448
Qt::DropActions EntryModel::supportedDragActions() const
449
{
450
    return (Qt::MoveAction | Qt::CopyAction);
451
}
452

453
Qt::ItemFlags EntryModel::flags(const QModelIndex& modelIndex) const
454
{
455
    if (!modelIndex.isValid()) {
456
        return Qt::NoItemFlags;
457
    } else {
458
        return QAbstractItemModel::flags(modelIndex) | Qt::ItemIsDragEnabled;
459
    }
460
}
461

462
QStringList EntryModel::mimeTypes() const
463
{
464
    QStringList types;
465
    types << QLatin1String("application/x-keepassx-entry");
466
    return types;
467
}
468

469
QMimeData* EntryModel::mimeData(const QModelIndexList& indexes) const
470
{
471
    if (indexes.isEmpty()) {
472
        return nullptr;
473
    }
474

475
    auto data = new QMimeData();
476
    QByteArray encoded;
477
    QDataStream stream(&encoded, QIODevice::WriteOnly);
478

479
    QSet<Entry*> seenEntries;
480

481
    for (const QModelIndex& index : indexes) {
482
        if (!index.isValid()) {
483
            continue;
484
        }
485

486
        Entry* entry = entryFromIndex(index);
487
        if (!seenEntries.contains(entry)) {
488
            // make sure we don't add entries multiple times when we get indexes
489
            // with the same row but different columns
490
            stream << entry->group()->database()->uuid() << entry->uuid();
491
            seenEntries.insert(entry);
492
        }
493
    }
494

495
    if (seenEntries.isEmpty()) {
496
        delete data;
497
        return nullptr;
498
    } else {
499
        data->setData(mimeTypes().at(0), encoded);
500
        return data;
501
    }
502
}
503

504
void EntryModel::entryAboutToAdd(Entry* entry)
505
{
506
    if (!m_group && !m_orgEntries.contains(entry)) {
507
        return;
508
    }
509

510
    beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size());
511
    if (!m_group) {
512
        m_entries.append(entry);
513
    }
514
}
515

516
void EntryModel::entryAdded(Entry* entry)
517
{
518
    if (!m_group && !m_orgEntries.contains(entry)) {
519
        return;
520
    }
521

522
    if (m_group) {
523
        m_entries = m_group->entries();
524
    }
525
    endInsertRows();
526
}
527

528
void EntryModel::entryAboutToRemove(Entry* entry)
529
{
530
    beginRemoveRows(QModelIndex(), m_entries.indexOf(entry), m_entries.indexOf(entry));
531
    if (!m_group) {
532
        m_entries.removeAll(entry);
533
    }
534
}
535

536
void EntryModel::entryRemoved()
537
{
538
    if (m_group) {
539
        m_entries = m_group->entries();
540
    }
541
    endRemoveRows();
542
}
543

544
void EntryModel::entryAboutToMoveUp(int row)
545
{
546
    beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1);
547
    if (m_group) {
548
        m_entries.move(row, row - 1);
549
    }
550
}
551

552
void EntryModel::entryMovedUp()
553
{
554
    if (m_group) {
555
        m_entries = m_group->entries();
556
    }
557
    endMoveRows();
558
}
559

560
void EntryModel::entryAboutToMoveDown(int row)
561
{
562
    beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2);
563
    if (m_group) {
564
        m_entries.move(row, row + 1);
565
    }
566
}
567

568
void EntryModel::entryMovedDown()
569
{
570
    if (m_group) {
571
        m_entries = m_group->entries();
572
    }
573
    endMoveRows();
574
}
575

576
void EntryModel::entryDataChanged(Entry* entry)
577
{
578
    int row = m_entries.indexOf(entry);
579
    emit dataChanged(index(row, 0), index(row, columnCount() - 1));
580
}
581

582
void EntryModel::onConfigChanged(Config::ConfigKey key)
583
{
584
    switch (key) {
585
    case Config::GUI_HideUsernames:
586
        emit dataChanged(index(0, Username), index(rowCount() - 1, Username), {Qt::DisplayRole});
587
        break;
588
    case Config::GUI_HidePasswords:
589
        emit dataChanged(index(0, Password), index(rowCount() - 1, Password), {Qt::DisplayRole});
590
        break;
591
    default:
592
        break;
593
    }
594
}
595

596
void EntryModel::severConnections()
597
{
598
    if (m_group) {
599
        disconnect(m_group, nullptr, this, nullptr);
600
    }
601

602
    for (const Group* group : asConst(m_allGroups)) {
603
        disconnect(group, nullptr, this, nullptr);
604
    }
605
}
606

607
void EntryModel::makeConnections(const Group* group)
608
{
609
    connect(group, SIGNAL(entryAboutToAdd(Entry*)), SLOT(entryAboutToAdd(Entry*)));
610
    connect(group, SIGNAL(entryAdded(Entry*)), SLOT(entryAdded(Entry*)));
611
    connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*)));
612
    connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved()));
613
    connect(group, SIGNAL(entryAboutToMoveUp(int)), SLOT(entryAboutToMoveUp(int)));
614
    connect(group, SIGNAL(entryMovedUp()), SLOT(entryMovedUp()));
615
    connect(group, SIGNAL(entryAboutToMoveDown(int)), SLOT(entryAboutToMoveDown(int)));
616
    connect(group, SIGNAL(entryMovedDown()), SLOT(entryMovedDown()));
617
    connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*)));
618
}
619
void EntryModel::setBackgroundColorVisible(bool visible)
620
{
621
    m_backgroundColorVisible = visible;
622
}
623

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.