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(qsizetype size)
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
Q_EMITQ_EMIT
T qobject_cast(QObject *object)
int width() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
bool isNull() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
ElideMiddle
PreferLocalFile
QString fileName(ComponentFormattingOptions options) const const
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
QString url(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 Tue Mar 26 2024 11:13:01 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.