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 <QFile>
23 #include <QGuiApplication>
24 #include <QMenu>
25 #include <QScreen>
26 
27 #include <KConfig>
28 #include <KConfigGroup>
29 #include <KLocalizedString>
30 
31 #include <set>
32 
34  : KSelectAction(parent)
35  , d_ptr(new KRecentFilesActionPrivate(this))
36 {
38  d->init();
39 }
40 
42  : KSelectAction(parent)
43  , d_ptr(new KRecentFilesActionPrivate(this))
44 {
46  d->init();
47 
48  // Want to keep the ampersands
49  setText(text);
50 }
51 
52 KRecentFilesAction::KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent)
53  : KSelectAction(parent)
54  , d_ptr(new KRecentFilesActionPrivate(this))
55 {
57  d->init();
58 
59  setIcon(icon);
60  // Want to keep the ampersands
61  setText(text);
62 }
63 
64 void KRecentFilesActionPrivate::init()
65 {
66  Q_Q(KRecentFilesAction);
67  delete q->menu();
68  q->setMenu(new QMenu());
69  q->setToolBarMode(KSelectAction::MenuMode);
70  m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
71  m_noEntriesAction->setObjectName(QStringLiteral("no_entries"));
72  m_noEntriesAction->setEnabled(false);
73  clearSeparator = q->menu()->addSeparator();
74  clearSeparator->setVisible(false);
75  clearSeparator->setObjectName(QStringLiteral("separator"));
76  clearAction = q->menu()->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Clear List"), q, &KRecentFilesAction::clear);
77  clearAction->setObjectName(QStringLiteral("clear_action"));
78  clearAction->setVisible(false);
79  q->setEnabled(false);
80  q->connect(q, qOverload<QAction *>(&KSelectAction::triggered), q, [this](QAction *action) {
81  urlSelected(action);
82  });
83 }
84 
86 
87 void KRecentFilesActionPrivate::urlSelected(QAction *action)
88 {
89  Q_Q(KRecentFilesAction);
90 
91  auto it = findByAction(action);
92 
93  Q_ASSERT(it != m_recentActions.cend()); // Should never happen
94 
95  Q_EMIT q->urlSelected(it->url);
96 }
97 
98 void KRecentFilesActionPrivate::removeAction(std::vector<RecentActionInfo>::iterator it)
99 {
100  Q_Q(KRecentFilesAction);
101  delete q->KSelectAction::removeAction(it->action);
102  m_recentActions.erase(it);
103 }
104 
105 int KRecentFilesAction::maxItems() const
106 {
107  Q_D(const KRecentFilesAction);
108  return d->m_maxItems;
109 }
110 
112 {
114  // set new maxItems
115  d->m_maxItems = std::max(maxItems, 0);
116 
117  // Remove all excess items, oldest (i.e. first added) first
118  const int difference = static_cast<int>(d->m_recentActions.size()) - d->m_maxItems;
119  if (difference > 0) {
120  auto beginIt = d->m_recentActions.begin();
121  auto endIt = d->m_recentActions.begin() + difference;
122  for (auto it = beginIt; it < endIt; ++it) {
123  // Remove the action from the menus, action groups ...etc
124  d->removeAction(it);
125  }
126  }
127 }
128 
129 static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)
130 {
131  // Calculate 3/4 of screen geometry, we do not want
132  // action titles to be bigger than that
133  // Since we do not know in which screen we are going to show
134  // we choose the min of all the screens
135  int maxWidthForTitles = INT_MAX;
136  const auto screens = QGuiApplication::screens();
137  for (QScreen *screen : screens) {
138  maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
139  }
140  const QFontMetrics fontMetrics = QFontMetrics(QFont());
141 
142  QString title = nameValue + QLatin1String(" [") + value + QLatin1Char(']');
143  const int nameWidth = fontMetrics.boundingRect(title).width();
144  if (nameWidth > maxWidthForTitles) {
145  // If it does not fit, try to cut only the whole path, though if the
146  // name is too long (more than 3/4 of the whole text) we cut it a bit too
147  const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
148  QString cutNameValue;
149  QString cutValue;
150  if (nameWidth > nameValueMaxWidth) {
151  cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
152  cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
153  } else {
154  cutNameValue = nameValue;
155  cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
156  }
157  title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']');
158  }
159  return title;
160 }
161 
162 void KRecentFilesAction::addUrl(const QUrl &url, const QString &name)
163 {
165 
166  // ensure we never add items if we want none
167  if (d->m_maxItems == 0) {
168  return;
169  }
170 
171  if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) {
172  return;
173  }
174 
175  // Remove url if it already exists in the list
176  removeUrl(url);
177 
178  // Remove oldest item if already maxItems in list
179  Q_ASSERT(d->m_maxItems > 0);
180  if (static_cast<int>(d->m_recentActions.size()) == d->m_maxItems) {
181  d->removeAction(d->m_recentActions.begin());
182  }
183 
184  const QString pathOrUrl(url.toDisplayString(QUrl::PreferLocalFile));
185  const QString tmpName = !name.isEmpty() ? name : url.fileName();
186 #ifdef Q_OS_WIN
187  const QString file = url.isLocalFile() ? QDir::toNativeSeparators(pathOrUrl) : pathOrUrl;
188 #else
189  const QString file = pathOrUrl;
190 #endif
191 
192  d->m_noEntriesAction->setVisible(false);
193  d->clearSeparator->setVisible(true);
194  d->clearAction->setVisible(true);
195  setEnabled(true);
196  // add file to list
197  const QString title = titleWithSensibleWidth(tmpName, file);
198  QAction *action = new QAction(title, selectableActionGroup());
199  addAction(action, url, tmpName);
200 }
201 
202 void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &name)
203 {
205 
206  menu()->insertAction(menu()->actions().value(0), action);
207  d->m_recentActions.push_back({action, url, name});
208 }
209 
211 {
213  auto it = d->findByAction(action);
214  Q_ASSERT(it != d->m_recentActions.cend());
215  d->m_recentActions.erase(it);
217 }
218 
220 {
222 
223  auto it = d->findByUrl(url);
224 
225  if (it != d->m_recentActions.cend()) {
226  d->removeAction(it);
227  };
228 }
229 
231 {
232  Q_D(const KRecentFilesAction);
233 
234  QList<QUrl> list;
235  list.reserve(d->m_recentActions.size());
236 
237  using Info = KRecentFilesActionPrivate::RecentActionInfo;
238  // Reverse order to match how the actions appear in the menu
239  std::transform(d->m_recentActions.crbegin(), d->m_recentActions.crend(), std::back_inserter(list), [](const Info &info) {
240  return info.url;
241  });
242 
243  return list;
244 }
245 
247 {
248  clearEntries();
250 }
251 
252 void KRecentFilesAction::clearEntries()
253 {
256  d->m_recentActions.clear();
257  d->m_noEntriesAction->setVisible(true);
258  d->clearSeparator->setVisible(false);
259  d->clearAction->setVisible(false);
260  setEnabled(false);
261 }
262 
264 {
266  clearEntries();
267 
268  QString key;
269  QString value;
270  QString nameKey;
271  QString nameValue;
272  QString title;
273  QUrl url;
274 
275  KConfigGroup cg = _config;
276  // "<default>" means the group was constructed with an empty name
277  if (cg.name() == QLatin1String("<default>")) {
278  cg = KConfigGroup(cg.config(), "RecentFiles");
279  }
280 
281  std::set<QUrl> seenUrls;
282 
283  bool thereAreEntries = false;
284  // read file list
285  for (int i = 1; i <= d->m_maxItems; i++) {
286  key = QStringLiteral("File%1").arg(i);
287  value = cg.readPathEntry(key, QString());
288  if (value.isEmpty()) {
289  continue;
290  }
291  url = QUrl::fromUserInput(value);
292 
293  // Don't restore if file doesn't exist anymore
294  if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
295  continue;
296  }
297 
298  auto [it, isNewUrl] = seenUrls.insert(url);
299  // Don't restore if this url has already been restored (e.g. broken config)
300  if (!isNewUrl) {
301  continue;
302  }
303 
304 #ifdef Q_OS_WIN
305  // convert to backslashes
306  if (url.isLocalFile()) {
307  value = QDir::toNativeSeparators(value);
308  }
309 #endif
310 
311  nameKey = QStringLiteral("Name%1").arg(i);
312  nameValue = cg.readPathEntry(nameKey, url.fileName());
313  title = titleWithSensibleWidth(nameValue, value);
314  if (!value.isNull()) {
315  thereAreEntries = true;
316  addAction(new QAction(title, selectableActionGroup()), url, nameValue);
317  }
318  }
319  if (thereAreEntries) {
320  d->m_noEntriesAction->setVisible(false);
321  d->clearSeparator->setVisible(true);
322  d->clearAction->setVisible(true);
323  setEnabled(true);
324  }
325 }
326 
328 {
330 
331  KConfigGroup cg = _cg;
332  // "<default>" means the group was constructed with an empty name
333  if (cg.name() == QLatin1String("<default>")) {
334  cg = KConfigGroup(cg.config(), "RecentFiles");
335  }
336 
337  cg.deleteGroup();
338 
339  // write file list
340  int i = 1;
341  for (const auto &[action, url, shortName] : d->m_recentActions) {
342  cg.writePathEntry(QStringLiteral("File%1").arg(i), url.toDisplayString(QUrl::PreferLocalFile));
343  cg.writePathEntry(QStringLiteral("Name%1").arg(i), shortName);
344 
345  ++i;
346  }
347 }
348 
349 #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 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.
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)
bool exists() const const
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
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)
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)
void triggered(const QString &text)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 19 2022 03:49:24 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.