KWidgetsAddons

krecentfilesmenu.cpp
1// This file is part of the KDE libraries
2// SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>
3// SPDX-License-Identifier: LGPL-2.1-or-later
4
5#include "krecentfilesmenu.h"
6
7#include <QGuiApplication>
8#include <QIcon>
9#include <QScreen>
10#include <QSettings>
11#include <QStandardPaths>
12
13class RecentFilesEntry
14{
15public:
16 QUrl url;
17 QString displayName;
18 QAction *action = nullptr;
19
20 QString titleWithSensibleWidth(QWidget *widget) const
21 {
22 const QString urlString = url.toDisplayString(QUrl::PreferLocalFile);
23 // Calculate 3/4 of screen geometry, we do not want
24 // action titles to be bigger than that
25 // Since we do not know in which screen we are going to show
26 // we choose the min of all the screens
27 int maxWidthForTitles = INT_MAX;
28 const auto screens = QGuiApplication::screens();
29 for (QScreen *screen : screens) {
30 maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
31 }
32
33 const QFontMetrics fontMetrics = widget->fontMetrics();
34
35 QString title = displayName + QLatin1String(" [") + urlString + QLatin1Char(']');
36 const int nameWidth = fontMetrics.boundingRect(title).width();
37 if (nameWidth > maxWidthForTitles) {
38 // If it does not fit, try to cut only the whole path, though if the
39 // name is too long (more than 3/4 of the whole text) we cut it a bit too
40 const int displayNameMaxWidth = maxWidthForTitles * 3 / 4;
41 QString cutNameValue;
42 QString cutValue;
43 if (nameWidth > displayNameMaxWidth) {
44 cutNameValue = fontMetrics.elidedText(displayName, Qt::ElideMiddle, displayNameMaxWidth);
45 cutValue = fontMetrics.elidedText(urlString, Qt::ElideMiddle, maxWidthForTitles - displayNameMaxWidth);
46 } else {
47 cutNameValue = displayName;
48 cutValue = fontMetrics.elidedText(urlString, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
49 }
50 title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']');
51 }
52 return title;
53 }
54
55 explicit RecentFilesEntry(const QUrl &_url, const QString &_displayName, KRecentFilesMenu *menu)
56 : url(_url)
57 , displayName(_displayName)
58 {
59 action = new QAction(titleWithSensibleWidth(menu));
60 QObject::connect(action, &QAction::triggered, action, [this, menu]() {
61 Q_EMIT menu->urlTriggered(url);
62 });
63 }
64
65 ~RecentFilesEntry()
66 {
67 delete action;
68 }
69};
70
71class KRecentFilesMenuPrivate
72{
73public:
74 explicit KRecentFilesMenuPrivate(KRecentFilesMenu *q_ptr);
75
76 std::vector<RecentFilesEntry *>::iterator findEntry(const QUrl &url);
77 void recentFilesChanged() const;
78
79 KRecentFilesMenu *const q;
80 QString m_group = QStringLiteral("RecentFiles");
81 std::vector<RecentFilesEntry *> m_entries;
82 QSettings *m_settings;
83 size_t m_maximumItems = 10;
84 QAction *m_noEntriesAction;
85 QAction *m_clearAction;
86};
87
88KRecentFilesMenuPrivate::KRecentFilesMenuPrivate(KRecentFilesMenu *q_ptr)
89 : q(q_ptr)
90{}
91
92std::vector<RecentFilesEntry *>::iterator KRecentFilesMenuPrivate::findEntry(const QUrl &url)
93{
94 return std::find_if(m_entries.begin(), m_entries.end(), [url](RecentFilesEntry *entry) {
95 return entry->url == url;
96 });
97}
98
99void KRecentFilesMenuPrivate::recentFilesChanged() const
100{
101 q->rebuildMenu();
102 Q_EMIT q->recentFilesChanged();
103}
104
105KRecentFilesMenu::KRecentFilesMenu(const QString &title, QWidget *parent)
106 : QMenu(title, parent)
107 , d(new KRecentFilesMenuPrivate(this))
108{
109 setIcon(QIcon::fromTheme(QStringLiteral("document-open-recent")));
110 const QString fileName =
112 d->m_settings = new QSettings(fileName, QSettings::Format::IniFormat, this);
113
114 d->m_noEntriesAction = new QAction(tr("No Entries"));
115 d->m_noEntriesAction->setDisabled(true);
116
117 d->m_clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), tr("Clear List"));
118
119 readFromFile();
120}
121
122KRecentFilesMenu::KRecentFilesMenu(QWidget *parent)
123 : KRecentFilesMenu(tr("Recent Files"), parent)
124{
125}
126
127KRecentFilesMenu::~KRecentFilesMenu()
128{
129 writeToFile();
130 qDeleteAll(d->m_entries);
131 delete d->m_clearAction;
132 delete d->m_noEntriesAction;
133}
134
135void KRecentFilesMenu::readFromFile()
136{
137 qDeleteAll(d->m_entries);
138 d->m_entries.clear();
139
140 d->m_settings->beginGroup(d->m_group);
141 const int size = d->m_settings->beginReadArray(QStringLiteral("files"));
142
143 d->m_entries.reserve(size);
144
145 for (int i = 0; i < size; ++i) {
146 d->m_settings->setArrayIndex(i);
147
148 const QUrl url = d->m_settings->value(QStringLiteral("url")).toUrl();
149 RecentFilesEntry *entry = new RecentFilesEntry(url, d->m_settings->value(QStringLiteral("displayName")).toString(), this);
150 d->m_entries.push_back(entry);
151 }
152
153 d->m_settings->endArray();
154 d->m_settings->endGroup();
155
156 d->recentFilesChanged();
157}
158
159void KRecentFilesMenu::addUrl(const QUrl &url, const QString &name)
160{
161 if (d->m_entries.size() == d->m_maximumItems) {
162 delete d->m_entries.back();
163 d->m_entries.pop_back();
164 }
165
166 // If it's already there remove the old one and reinsert so it appears as new
167 auto it = d->findEntry(url);
168 if (it != d->m_entries.cend()) {
169 delete *it;
170 d->m_entries.erase(it);
171 }
172
173 QString displayName = name;
174
175 if (displayName.isEmpty()) {
176 displayName = url.fileName();
177 }
178
179 RecentFilesEntry *entry = new RecentFilesEntry(url, displayName, this);
180 d->m_entries.insert(d->m_entries.begin(), entry);
181
182 d->recentFilesChanged();
183}
184
186{
187 auto it = d->findEntry(url);
188
189 if (it == d->m_entries.end()) {
190 return;
191 }
192
193 delete *it;
194 d->m_entries.erase(it);
195
196 d->recentFilesChanged();
197}
198
199void KRecentFilesMenu::rebuildMenu()
200{
201 clear();
202
203 if (d->m_entries.empty()) {
204 addAction(d->m_noEntriesAction);
205 return;
206 }
207
208 for (const RecentFilesEntry *entry : d->m_entries) {
209 addAction(entry->action);
210 }
211
212 addSeparator();
213 addAction(d->m_clearAction);
214
215 // reconnect d->m_clearAction, since it was disconnected in clear()
217}
218
219void KRecentFilesMenu::writeToFile()
220{
221 d->m_settings->remove(QString());
222 d->m_settings->beginGroup(d->m_group);
223 d->m_settings->beginWriteArray(QStringLiteral("files"));
224
225 int index = 0;
226 for (const RecentFilesEntry *entry : d->m_entries) {
227 d->m_settings->setArrayIndex(index);
228 d->m_settings->setValue(QStringLiteral("url"), entry->url);
229 d->m_settings->setValue(QStringLiteral("displayName"), entry->displayName);
230 ++index;
231 }
232
233 d->m_settings->endArray();
234 d->m_settings->endGroup();
235 d->m_settings->sync();
236}
237
239{
240 return d->m_group;
241}
242
244{
245 d->m_group = group;
246 readFromFile();
247}
248
250{
251 return d->m_maximumItems;
252}
253
254void KRecentFilesMenu::setMaximumItems(size_t maximumItems)
255{
256 d->m_maximumItems = maximumItems;
257
258 // Truncate if there are more entries than the new maximum
259 if (d->m_entries.size() > maximumItems) {
260 qDeleteAll(d->m_entries.begin() + maximumItems, d->m_entries.end());
261 d->m_entries.erase(d->m_entries.begin() + maximumItems, d->m_entries.end());
262
263 d->recentFilesChanged();
264 }
265}
266
268{
269 QList<QUrl> urls;
270 urls.reserve(d->m_entries.size());
271 std::transform(d->m_entries.cbegin(), d->m_entries.cend(), std::back_inserter(urls), [](const RecentFilesEntry *entry) {
272 return entry->url;
273 });
274
275 return urls;
276}
277
279{
280 qDeleteAll(d->m_entries);
281 d->m_entries.clear();
282
283 d->recentFilesChanged();
284}
285
286#include "moc_krecentfilesmenu.cpp"
A menu that offers a set of recent files.
void removeUrl(const QUrl &url)
Remove a URL from the recent files list.
QList< QUrl > recentFiles() const
List of URLs of recent files.
void urlTriggered(const QUrl &url)
emitted when the user clicks on a file action.
void setGroup(const QString &group)
Specify a group for storing the URLs.
void setMaximumItems(size_t maximumItems)
Set the maximum URL count.
int maximumItems() const
The maximum number of files this menu can hold.
void clearRecentFiles()
Clear recent files list.
void addUrl(const QUrl &url, const QString &name=QString())
Add URL to recent files list.
QString group() const
The group the URLs are saved to/read from.
void recentFilesChanged()
Emitted when the recent files list has been changed.
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
char * toString(const EngineQuery &query)
void triggered(bool checked)
QRect boundingRect(QChar ch) const const
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
QList< QScreen * > screens()
QIcon fromTheme(const QString &name)
void reserve(qsizetype size)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSeparator()
void clear()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
int width() const const
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
bool isEmpty() const const
ElideMiddle
PreferLocalFile
QString fileName(ComponentFormattingOptions options) const const
QString toDisplayString(FormattingOptions options) const const
QFontMetrics fontMetrics() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.