KConfigWidgets

krecentfilesaction.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <[email protected]>
4  SPDX-FileCopyrightText: 1999 Simon Hausmann <[email protected]>
5  SPDX-FileCopyrightText: 2000 Nicolas Hadacek <[email protected]>
6  SPDX-FileCopyrightText: 2000 Kurt Granroth <[email protected]>
7  SPDX-FileCopyrightText: 2000 Michael Koch <[email protected]>
8  SPDX-FileCopyrightText: 2001 Holger Freyther <[email protected]>
9  SPDX-FileCopyrightText: 2002 Ellis Whitehead <[email protected]>
10  SPDX-FileCopyrightText: 2002 Joseph Wenninger <[email protected]>
11  SPDX-FileCopyrightText: 2003 Andras Mantia <[email protected]>
12  SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <[email protected]>
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 #include <KConfig>
29 #include <KConfigGroup>
30 #include <KLocalizedString>
31 
32 #include <set>
33 
35  : KSelectAction(parent)
36  , d_ptr(new KRecentFilesActionPrivate(this))
37 {
39  d->init();
40 }
41 
43  : KSelectAction(parent)
44  , d_ptr(new KRecentFilesActionPrivate(this))
45 {
47  d->init();
48 
49  // Want to keep the ampersands
50  setText(text);
51 }
52 
53 KRecentFilesAction::KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent)
54  : KSelectAction(parent)
55  , d_ptr(new KRecentFilesActionPrivate(this))
56 {
58  d->init();
59 
60  setIcon(icon);
61  // Want to keep the ampersands
62  setText(text);
63 }
64 
65 void KRecentFilesActionPrivate::init()
66 {
67  Q_Q(KRecentFilesAction);
68  delete q->menu();
69  q->setMenu(new QMenu());
70  q->setToolBarMode(KSelectAction::MenuMode);
71  m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
72  m_noEntriesAction->setObjectName(QStringLiteral("no_entries"));
73  m_noEntriesAction->setEnabled(false);
74  clearSeparator = q->menu()->addSeparator();
75  clearSeparator->setVisible(false);
76  clearSeparator->setObjectName(QStringLiteral("separator"));
77  clearAction = q->menu()->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Clear List"), q, &KRecentFilesAction::clear);
78  clearAction->setObjectName(QStringLiteral("clear_action"));
79  clearAction->setVisible(false);
80  q->setEnabled(false);
81  q->connect(q, &KSelectAction::actionTriggered, q, [this](QAction *action) {
82  urlSelected(action);
83  });
84 }
85 
87 
88 void KRecentFilesActionPrivate::urlSelected(QAction *action)
89 {
90  Q_Q(KRecentFilesAction);
91 
92  auto it = findByAction(action);
93 
94  Q_ASSERT(it != m_recentActions.cend()); // Should never happen
95 
96  const QUrl url = it->url; // BUG: 461448; see iterator invalidation rules
97  Q_EMIT q->urlSelected(url);
98 }
99 
100 void KRecentFilesActionPrivate::removeAction(std::vector<RecentActionInfo>::iterator it)
101 {
102  Q_Q(KRecentFilesAction);
103  delete q->KSelectAction::removeAction(it->action);
104  m_recentActions.erase(it);
105 }
106 
107 int KRecentFilesAction::maxItems() const
108 {
109  Q_D(const KRecentFilesAction);
110  return d->m_maxItems;
111 }
112 
114 {
116  // set new maxItems
117  d->m_maxItems = std::max(maxItems, 0);
118 
119  // Remove all excess items, oldest (i.e. first added) first
120  const int difference = static_cast<int>(d->m_recentActions.size()) - d->m_maxItems;
121  if (difference > 0) {
122  auto beginIt = d->m_recentActions.begin();
123  auto endIt = d->m_recentActions.begin() + difference;
124  for (auto it = beginIt; it < endIt; ++it) {
125  // Remove the action from the menus, action groups ...etc
126  d->removeAction(it);
127  }
128  }
129 }
130 
131 static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)
132 {
133  // Calculate 3/4 of screen geometry, we do not want
134  // action titles to be bigger than that
135  // Since we do not know in which screen we are going to show
136  // we choose the min of all the screens
137  int maxWidthForTitles = INT_MAX;
138  const auto screens = QGuiApplication::screens();
139  for (QScreen *screen : screens) {
140  maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
141  }
142  const QFontMetrics fontMetrics = QFontMetrics(QFont());
143 
144  QString title = nameValue + QLatin1String(" [") + value + QLatin1Char(']');
145  const int nameWidth = fontMetrics.boundingRect(title).width();
146  if (nameWidth > maxWidthForTitles) {
147  // If it does not fit, try to cut only the whole path, though if the
148  // name is too long (more than 3/4 of the whole text) we cut it a bit too
149  const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
150  QString cutNameValue;
151  QString cutValue;
152  if (nameWidth > nameValueMaxWidth) {
153  cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
154  cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
155  } else {
156  cutNameValue = nameValue;
157  cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
158  }
159  title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']');
160  }
161  return title;
162 }
163 
164 void KRecentFilesAction::addUrl(const QUrl &url, const QString &name)
165 {
167 
168  // ensure we never add items if we want none
169  if (d->m_maxItems == 0) {
170  return;
171  }
172 
173  if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) {
174  return;
175  }
176 
177  // Remove url if it already exists in the list
178  removeUrl(url);
179 
180  // Remove oldest item if already maxItems in list
181  Q_ASSERT(d->m_maxItems > 0);
182  if (static_cast<int>(d->m_recentActions.size()) == d->m_maxItems) {
183  d->removeAction(d->m_recentActions.begin());
184  }
185 
186  const QString pathOrUrl(url.toDisplayString(QUrl::PreferLocalFile));
187  const QString tmpName = !name.isEmpty() ? name : url.fileName();
188 #ifdef Q_OS_WIN
189  const QString file = url.isLocalFile() ? QDir::toNativeSeparators(pathOrUrl) : pathOrUrl;
190 #else
191  const QString file = pathOrUrl;
192 #endif
193 
194  d->m_noEntriesAction->setVisible(false);
195  d->clearSeparator->setVisible(true);
196  d->clearAction->setVisible(true);
197  setEnabled(true);
198  // add file to list
199  const QString title = titleWithSensibleWidth(tmpName, file);
200 
201  QAction *action = new QAction(title, selectableActionGroup());
202  addAction(action, url, tmpName);
203 }
204 
205 void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &name)
206 {
208 
210  if (!mimeType.isDefault()) {
211  action->setIcon(QIcon::fromTheme(mimeType.iconName()));
212  }
213  menu()->insertAction(menu()->actions().value(0), action);
214  d->m_recentActions.push_back({action, url, name});
215 }
216 
218 {
220  auto it = d->findByAction(action);
221  Q_ASSERT(it != d->m_recentActions.cend());
222  d->m_recentActions.erase(it);
224 }
225 
227 {
229 
230  auto it = d->findByUrl(url);
231 
232  if (it != d->m_recentActions.cend()) {
233  d->removeAction(it);
234  };
235 }
236 
238 {
239  Q_D(const KRecentFilesAction);
240 
241  QList<QUrl> list;
242  list.reserve(d->m_recentActions.size());
243 
244  using Info = KRecentFilesActionPrivate::RecentActionInfo;
245  // Reverse order to match how the actions appear in the menu
246  std::transform(d->m_recentActions.crbegin(), d->m_recentActions.crend(), std::back_inserter(list), [](const Info &info) {
247  return info.url;
248  });
249 
250  return list;
251 }
252 
254 {
255  clearEntries();
257 }
258 
259 void KRecentFilesAction::clearEntries()
260 {
263  d->m_recentActions.clear();
264  d->m_noEntriesAction->setVisible(true);
265  d->clearSeparator->setVisible(false);
266  d->clearAction->setVisible(false);
267  setEnabled(false);
268 }
269 
271 {
273  clearEntries();
274 
275  QString key;
276  QString value;
277  QString nameKey;
278  QString nameValue;
279  QString title;
280  QUrl url;
281 
282  KConfigGroup cg = _config;
283  // "<default>" means the group was constructed with an empty name
284  if (cg.name() == QLatin1String("<default>")) {
285  cg = KConfigGroup(cg.config(), "RecentFiles");
286  }
287 
288  std::set<QUrl> seenUrls;
289 
290  bool thereAreEntries = false;
291  // read file list
292  for (int i = 1; i <= d->m_maxItems; i++) {
293  key = QStringLiteral("File%1").arg(i);
294  value = cg.readPathEntry(key, QString());
295  if (value.isEmpty()) {
296  continue;
297  }
298  url = QUrl::fromUserInput(value);
299 
300  auto [it, isNewUrl] = seenUrls.insert(url);
301  // Don't restore if this url has already been restored (e.g. broken config)
302  if (!isNewUrl) {
303  continue;
304  }
305 
306 #ifdef Q_OS_WIN
307  // convert to backslashes
308  if (url.isLocalFile()) {
309  value = QDir::toNativeSeparators(value);
310  }
311 #endif
312 
313  nameKey = QStringLiteral("Name%1").arg(i);
314  nameValue = cg.readPathEntry(nameKey, url.fileName());
315  title = titleWithSensibleWidth(nameValue, value);
316  if (!value.isNull()) {
317  thereAreEntries = true;
318  addAction(new QAction(title, selectableActionGroup()), url, nameValue);
319  }
320  }
321  if (thereAreEntries) {
322  d->m_noEntriesAction->setVisible(false);
323  d->clearSeparator->setVisible(true);
324  d->clearAction->setVisible(true);
325  setEnabled(true);
326  }
327 }
328 
330 {
332 
333  KConfigGroup cg = _cg;
334  // "<default>" means the group was constructed with an empty name
335  if (cg.name() == QLatin1String("<default>")) {
336  cg = KConfigGroup(cg.config(), "RecentFiles");
337  }
338 
339  cg.deleteGroup();
340 
341  // write file list
342  int i = 1;
343  for (const auto &[action, url, shortName] : d->m_recentActions) {
344  cg.writePathEntry(QStringLiteral("File%1").arg(i), url.toDisplayString(QUrl::PreferLocalFile));
345  cg.writePathEntry(QStringLiteral("Name%1").arg(i), shortName);
346 
347  ++i;
348  }
349 }
350 
351 #include "moc_krecentfilesaction.cpp"
void removeUrl(const QUrl &url)
Remove an URL from the recent files list.
bool isNull() const const
Q_EMITQ_EMIT
void actionTriggered(QAction *action)
void loadEntries(const KConfigGroup &config)
Loads the recent files entries from a given KConfigGroup object.
QList< QScreen * > screens()
void recentListCleared()
This signal gets emitted when the user clear list.
QString url(QUrl::FormattingOptions options) const const
QList< QUrl > urls() const
Retrieve a list of all URLs in the recent files list.
QIcon fromTheme(const QString &name)
void saveEntries(const KConfigGroup &config)
Saves the current recent files entries to a given KConfigGroup object.
void addAction(QAction *action, const QUrl &url, const QString &name)
Adds action to the list of URLs, with url and title name.
int width() const const
QRect boundingRect(QChar ch) const const
KRecentFilesAction(QObject *parent)
Constructs an action with the specified parent.
virtual void clear()
Clears the recent files list.
QAction(QObject *parent)
void deleteGroup(const char *group, WriteConfigFlags flags=Normal)
QList< QAction * > actions() const
void insertAction(QAction *before, QAction *action)
QString tempPath()
void reserve(int alloc)
QMenu * menu() const const
void setIcon(const QIcon &icon)
QString i18n(const char *text, const TYPE &arg...)
PreferLocalFile
bool isEmpty() const const
QString fileName(QUrl::ComponentFormattingOptions options) const const
QString toNativeSeparators(const QString &pathName)
QString toDisplayString(QUrl::FormattingOptions options) const const
void setText(const QString &text)
QString toLocalFile() const const
Recent files action.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
ElideMiddle
QString path(QUrl::ComponentFormattingOptions options) const const
void setEnabled(bool)
~KRecentFilesAction() override
Destructor.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QAction * action(const QString &text, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
bool isLocalFile() const const
void setMaxItems(int maxItems)
Sets the maximum of items in the recent files list.
void writePathEntry(const char *Key, const QString &path, WriteConfigFlags pFlags=Normal)
QMimeType mimeTypeForFile(const QString &fileName, QMimeDatabase::MatchMode mode) const const
virtual QAction * removeAction(QAction *action)
KConfig * config()
QString name() const
QString readPathEntry(const char *key, const QString &aDefault) const
QAction * removeAction(QAction *action) override
Reimplemented for internal reasons.
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
QActionGroup * selectableActionGroup() const
void addUrl(const QUrl &url, const QString &name=QString())
Add URL to the recent files list.
QUrl fromUserInput(const QString &userInput)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Jun 9 2023 04:08:58 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.