1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "recentfiles.h" |
5 | #include "globals.h" |
6 | |
7 | #include <QtCore/QDebug> |
8 | #include <QtCore/QFileInfo> |
9 | #include <QtCore/QSettings> |
10 | #include <QtCore/QString> |
11 | #include <QtCore/QStringList> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | static QString configKey() |
16 | { |
17 | return settingPath("RecentlyOpenedFiles" ); |
18 | } |
19 | |
20 | |
21 | RecentFiles::RecentFiles(const int maxEntries) |
22 | : m_groupOpen(false), |
23 | m_clone1st(false), |
24 | m_maxEntries(maxEntries) |
25 | { |
26 | m_timer.setSingleShot(true); |
27 | m_timer.setInterval(3 * 60 * 1000); |
28 | connect(sender: &m_timer, signal: &QTimer::timeout, |
29 | context: this, slot: &RecentFiles::closeGroup); |
30 | } |
31 | |
32 | /* |
33 | * The logic is as follows: |
34 | * - The most recent (i.e., topmost) item can be open ("in flux") |
35 | * - The item is closed by either a timeout (3 min or so) or a |
36 | * "terminal action" (e.g., closing all files) |
37 | * - While the item is open, modifications to the set of open files |
38 | * will modify that item instead of creating new items |
39 | * - If the open item is modified to be equal to an existing item, |
40 | * the existing item is deleted, but will be re-created when the |
41 | * open item is modified even further |
42 | * Cases (actions in parentheses are no-ops): |
43 | * - identical to top item => (do nothing) |
44 | * - closed, new item => insert at top, (clear marker) |
45 | * - closed, existing item => move to top, mark for cloning |
46 | * - open, new item, not marked => replace top, (clear marker) |
47 | * - open, new item, marked => insert at top, clear marker |
48 | * - open, existing item, not marked => replace top, delete copy, mark for cloning |
49 | * - open, existing item, marked => insert at top, delete copy, (mark for cloning) |
50 | * - closing clears marker |
51 | */ |
52 | void RecentFiles::addFiles(const QStringList &names) |
53 | { |
54 | if (m_strLists.isEmpty() || names != m_strLists.first()) { |
55 | if (m_groupOpen && !m_clone1st) |
56 | // Group being open implies at least one item in the list |
57 | m_strLists.removeFirst(); |
58 | m_groupOpen = true; |
59 | |
60 | // We do *not* sort the actual entries, as that would destroy the user's |
61 | // chosen arrangement. However, we do the searching on sorted lists, so |
62 | // we throw out (probably) obsolete arrangements. |
63 | QList<QStringList> sortedLists = m_strLists; |
64 | for (int i = 0; i < sortedLists.size(); ++i) |
65 | sortedLists[i].sort(); |
66 | QStringList sortedNames = names; |
67 | sortedNames.sort(); |
68 | |
69 | int index = sortedLists.indexOf(t: sortedNames); |
70 | if (index >= 0) { |
71 | m_strLists.removeAt(i: index); |
72 | m_clone1st = true; |
73 | } else { |
74 | if (m_strLists.size() >= m_maxEntries) |
75 | m_strLists.removeLast(); |
76 | m_clone1st = false; |
77 | } |
78 | m_strLists.prepend(t: names); |
79 | } |
80 | m_timer.start(); |
81 | } |
82 | |
83 | void RecentFiles::closeGroup() |
84 | { |
85 | m_timer.stop(); |
86 | m_groupOpen = false; |
87 | } |
88 | |
89 | void RecentFiles::readConfig() |
90 | { |
91 | m_strLists.clear(); |
92 | QVariant val = QSettings().value(key: configKey()); |
93 | if (val.metaType().id() == QMetaType::QVariantList) { |
94 | const auto list = val.toList(); |
95 | for (const QVariant &v : list) |
96 | m_strLists << v.toStringList(); |
97 | } |
98 | } |
99 | |
100 | void RecentFiles::writeConfig() const |
101 | { |
102 | QList<QVariant> vals; |
103 | for (const QStringList &sl : m_strLists) |
104 | vals << sl; |
105 | QSettings().setValue(key: configKey(), value: vals); |
106 | } |
107 | |
108 | QT_END_NAMESPACE |
109 | |