KConfigWidgets

krecentfilesaction.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
4 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
5 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
6 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
7 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
8 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
9 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
10 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
11 SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
12 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
13
14 SPDX-License-Identifier: LGPL-2.0-only
15*/
16
17#include "krecentfilesaction.h"
18#include "krecentfilesaction_p.h"
19
20#include <QActionGroup>
21#include <QDir>
22#include <QGuiApplication>
23#include <QMenu>
24#include <QMimeDatabase>
25#include <QMimeType>
26#include <QScreen>
27
28#ifdef QT_DBUS_LIB
29#include <QDBusConnectionInterface>
30#include <QDBusInterface>
31#include <QDBusMessage>
32#endif
33
34#include <KConfig>
35#include <KConfigGroup>
36#include <KLocalizedString>
37#include <KShell>
38
39#include <set>
40
42 : KSelectAction(parent)
43 , d_ptr(new KRecentFilesActionPrivate(this))
44{
46 d->init();
47}
48
50 : KSelectAction(parent)
51 , d_ptr(new KRecentFilesActionPrivate(this))
52{
54 d->init();
55
56 // Want to keep the ampersands
58}
59
61 : KSelectAction(parent)
62 , d_ptr(new KRecentFilesActionPrivate(this))
63{
65 d->init();
66
68 // Want to keep the ampersands
70}
71
72void KRecentFilesActionPrivate::init()
73{
75 delete q->menu();
76 q->setMenu(new QMenu());
77 q->setToolBarMode(KSelectAction::MenuMode);
78 m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
79 m_noEntriesAction->setObjectName(QStringLiteral("no_entries"));
80 m_noEntriesAction->setEnabled(false);
81 clearSeparator = q->menu()->addSeparator();
82 clearSeparator->setVisible(false);
83 clearSeparator->setObjectName(QStringLiteral("separator"));
84 clearAction = q->menu()->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Clear List"), q, &KRecentFilesAction::clear);
85 clearAction->setObjectName(QStringLiteral("clear_action"));
86 clearAction->setVisible(false);
87 q->setEnabled(false);
88 q->connect(q, &KSelectAction::actionTriggered, q, [this](QAction *action) {
89 urlSelected(action);
90 });
91}
92
94
95void KRecentFilesActionPrivate::urlSelected(QAction *action)
96{
98
99 auto it = findByAction(action);
100
101 Q_ASSERT(it != m_recentActions.cend()); // Should never happen
102
103 const QUrl url = it->url; // BUG: 461448; see iterator invalidation rules
104 Q_EMIT q->urlSelected(url);
105}
106
107// TODO: remove this helper function, it will crash if you use it in a loop
108void KRecentFilesActionPrivate::removeAction(std::vector<RecentActionInfo>::iterator it)
109{
111 delete q->KSelectAction::removeAction(it->action);
112 m_recentActions.erase(it);
113}
114
115int KRecentFilesAction::maxItems() const
116{
117 Q_D(const KRecentFilesAction);
118 return d->m_maxItems;
119}
120
122{
124 // set new maxItems
125 d->m_maxItems = std::max(maxItems, 0);
126
127 // Remove all excess items, oldest (i.e. first added) first
128 const int difference = static_cast<int>(d->m_recentActions.size()) - d->m_maxItems;
129 if (difference > 0) {
130 auto beginIt = d->m_recentActions.begin();
131 auto endIt = d->m_recentActions.begin() + difference;
132 for (auto it = beginIt; it < endIt; ++it) {
133 // Remove the action from the menus, action groups ...etc
134 delete KSelectAction::removeAction(it->action);
135 }
136 d->m_recentActions.erase(beginIt, endIt);
137 }
138}
139
140static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)
141{
142 // Calculate 3/4 of screen geometry, we do not want
143 // action titles to be bigger than that
144 // Since we do not know in which screen we are going to show
145 // we choose the min of all the screens
146 int maxWidthForTitles = INT_MAX;
147 const auto screens = QGuiApplication::screens();
148 for (QScreen *screen : screens) {
149 maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
150 }
151 const QFontMetrics fontMetrics = QFontMetrics(QFont());
152
153 QString title = nameValue + QLatin1String(" [") + value + QLatin1Char(']');
154 const int nameWidth = fontMetrics.boundingRect(title).width();
155 if (nameWidth > maxWidthForTitles) {
156 // If it does not fit, try to cut only the whole path, though if the
157 // name is too long (more than 3/4 of the whole text) we cut it a bit too
158 const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
159 QString cutNameValue;
160 QString cutValue;
161 if (nameWidth > nameValueMaxWidth) {
162 cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
163 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
164 } else {
165 cutNameValue = nameValue;
166 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
167 }
168 title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']');
169 }
170 return title;
171}
172
173void KRecentFilesAction::addUrl(const QUrl &url, const QString &name)
174{
176
177 // ensure we never add items if we want none
178 if (d->m_maxItems == 0) {
179 return;
180 }
181
182 if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) {
183 return;
184 }
185
186 // Remove url if it already exists in the list
187 removeUrl(url);
188
189 // Remove oldest item if already maxItems in list
190 Q_ASSERT(d->m_maxItems > 0);
191 if (static_cast<int>(d->m_recentActions.size()) == d->m_maxItems) {
192 d->removeAction(d->m_recentActions.begin());
193 }
194
196 const QString tmpName = !name.isEmpty() ? name : url.fileName();
197#ifdef Q_OS_WIN
199#else
200 const QString file = pathOrUrl;
201#endif
202
203 d->m_noEntriesAction->setVisible(false);
204 d->clearSeparator->setVisible(true);
205 d->clearAction->setVisible(true);
206 setEnabled(true);
207 // add file to list
208 const QString title = titleWithSensibleWidth(tmpName, KShell::tildeCollapse(file));
209
211
212#ifdef QT_DBUS_LIB
213 static bool isKdeSession = qgetenv("XDG_CURRENT_DESKTOP") == "KDE";
214 if (isKdeSession) {
216 if (bus.isConnected() && bus.interface()->isServiceRegistered(QStringLiteral("org.kde.ActivityManager"))) {
217 const static QString activityService = QStringLiteral("org.kde.ActivityManager");
218 const static QString activityResources = QStringLiteral("/ActivityManager/Resources");
219 const static QString activityResouceInferface = QStringLiteral("org.kde.ActivityManager.Resources");
220
221 const auto urlString = url.toString(QUrl::PreferLocalFile);
222 QDBusMessage message =
224 message.setArguments({qApp->desktopFileName(), uint(0) /* WinId */, urlString, uint(0) /* eventType Accessed */});
225 bus.asyncCall(message);
226
227 message = QDBusMessage::createMethodCall(activityService, activityResources, activityResouceInferface, QStringLiteral("RegisterResourceMimetype"));
228 message.setArguments({urlString, mimeType.name()});
229 bus.asyncCall(message);
230
231 message = QDBusMessage::createMethodCall(activityService, activityResources, activityResouceInferface, QStringLiteral("RegisterResourceTitle"));
232 message.setArguments({urlString, url.fileName()});
233 bus.asyncCall(message);
234 }
235 }
236#endif
237
239 addAction(action, url, tmpName, mimeType);
240}
241
242void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &name, const QMimeType &_mimeType)
243{
245
246 auto mimeType = _mimeType;
247 if (!mimeType.isValid()) {
249 }
250
251 if (!mimeType.isDefault()) {
252 action->setIcon(QIcon::fromTheme(mimeType.iconName()));
253 }
254
255 menu()->insertAction(menu()->actions().value(0), action);
256 d->m_recentActions.push_back({action, url, name});
257}
258
260{
262 auto it = d->findByAction(action);
263 Q_ASSERT(it != d->m_recentActions.cend());
264 d->m_recentActions.erase(it);
266}
267
269{
271
272 auto it = d->findByUrl(url);
273
274 if (it != d->m_recentActions.cend()) {
275 d->removeAction(it);
276 };
277}
278
280{
281 Q_D(const KRecentFilesAction);
282
283 QList<QUrl> list;
284 list.reserve(d->m_recentActions.size());
285
286 using Info = KRecentFilesActionPrivate::RecentActionInfo;
287 // Reverse order to match how the actions appear in the menu
288 std::transform(d->m_recentActions.crbegin(), d->m_recentActions.crend(), std::back_inserter(list), [](const Info &info) {
289 return info.url;
290 });
291
292 return list;
293}
294
296{
297 clearEntries();
299}
300
301void KRecentFilesAction::clearEntries()
302{
305 d->m_recentActions.clear();
306 d->m_noEntriesAction->setVisible(true);
307 d->clearSeparator->setVisible(false);
308 d->clearAction->setVisible(false);
309 setEnabled(false);
310}
311
313{
315 clearEntries();
316
317 QString key;
318 QString value;
321 QString title;
322 QUrl url;
323
325 // "<default>" means the group was constructed with an empty name
326 if (cg.name() == QLatin1String("<default>")) {
327 cg = KConfigGroup(cg.config(), QStringLiteral("RecentFiles"));
328 }
329
330 std::set<QUrl> seenUrls;
331
332 bool thereAreEntries = false;
333 // read file list
334 for (int i = 1; i <= d->m_maxItems; i++) {
335 key = QStringLiteral("File%1").arg(i);
336 value = cg.readPathEntry(key, QString());
337 if (value.isEmpty()) {
338 continue;
339 }
340 url = QUrl::fromUserInput(value);
341
342 auto [it, isNewUrl] = seenUrls.insert(url);
343 // Don't restore if this url has already been restored (e.g. broken config)
344 if (!isNewUrl) {
345 continue;
346 }
347
348#ifdef Q_OS_WIN
349 // convert to backslashes
350 if (url.isLocalFile()) {
351 value = QDir::toNativeSeparators(value);
352 }
353#endif
354
355 nameKey = QStringLiteral("Name%1").arg(i);
356 nameValue = cg.readPathEntry(nameKey, url.fileName());
357 title = titleWithSensibleWidth(nameValue, KShell::tildeCollapse(value));
358 if (!value.isNull()) {
359 thereAreEntries = true;
361 }
362 }
363 if (thereAreEntries) {
364 d->m_noEntriesAction->setVisible(false);
365 d->clearSeparator->setVisible(true);
366 d->clearAction->setVisible(true);
367 setEnabled(true);
368 }
369}
370
372{
374
376 // "<default>" means the group was constructed with an empty name
377 if (cg.name() == QLatin1String("<default>")) {
378 cg = KConfigGroup(cg.config(), QStringLiteral("RecentFiles"));
379 }
380
381 cg.deleteGroup();
382
383 // write file list
384 int i = 1;
385 for (const auto &[action, url, shortName] : d->m_recentActions) {
386 cg.writePathEntry(QStringLiteral("File%1").arg(i), url.toDisplayString(QUrl::PreferLocalFile));
387 cg.writePathEntry(QStringLiteral("Name%1").arg(i), shortName);
388
389 ++i;
390 }
391}
392
393#include "moc_krecentfilesaction.cpp"
Recent files action.
void addAction(QAction *action, const QUrl &url, const QString &name, const QMimeType &mimeType=QMimeType())
Adds action to the list of URLs, with url and title name.
KRecentFilesAction(QObject *parent)
Constructs an action with the specified parent.
void removeUrl(const QUrl &url)
Remove an URL from the recent files list.
QAction * removeAction(QAction *action) override
Reimplemented for internal reasons.
QList< QUrl > urls() const
Retrieve a list of all URLs in the recent files list.
void recentListCleared()
This signal gets emitted when the user clear list.
void addUrl(const QUrl &url, const QString &name=QString())
Add URL to the recent files list.
void setMaxItems(int maxItems)
Sets the maximum of items in the recent files list.
~KRecentFilesAction() override
Destructor.
void loadEntries(const KConfigGroup &config)
Loads the recent files entries from a given KConfigGroup object.
void saveEntries(const KConfigGroup &config)
Saves the current recent files entries to a given KConfigGroup object.
virtual void clear()
Clears the recent files list.
virtual QAction * removeAction(QAction *action)
QList< QAction * > actions() const
QActionGroup * selectableActionGroup() const
QAction * action(const QString &text, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
void actionTriggered(QAction *action)
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QString tildeCollapse(const QString &path)
QAction(QObject *parent)
void setEnabled(bool)
void setIcon(const QIcon &icon)
QMenu * menu() const const
void setText(const QString &text)
QDBusPendingCall asyncCall(const QDBusMessage &message, int timeout) const const
QDBusConnectionInterface * interface() const const
bool isConnected() const const
QDBusConnection sessionBus()
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
void setArguments(const QList< QVariant > &arguments)
QString tempPath()
QString toNativeSeparators(const QString &pathName)
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(int alloc)
QMimeType mimeTypeForFile(const QString &fileName, QMimeDatabase::MatchMode mode) const const
Q_EMITQ_EMIT
T qobject_cast(QObject *object)
int width() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const
bool isNull() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
ElideMiddle
PreferLocalFile
QString fileName(QUrl::ComponentFormattingOptions options) const const
QUrl fromUserInput(const QString &userInput)
bool isLocalFile() const const
QString path(QUrl::ComponentFormattingOptions options) const const
QString toDisplayString(QUrl::FormattingOptions options) const const
QString toLocalFile() const const
QString toString(QUrl::FormattingOptions options) const const
QString url(QUrl::FormattingOptions options) const const
void insertAction(QAction *before, QAction *action)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:51 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.