KWidgetsAddons

krecentfilesmenu.cpp
1 // This file is part of the KDE libraries
2 // SPDX-FileCopyrightText: 2020 Nicolas Fella <[email protected]>
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 
13 class RecentFilesEntry
14 {
15 public:
16  QUrl url;
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 
71 class KRecentFilesMenuPrivate
72 {
73 public:
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 
88 KRecentFilesMenuPrivate::KRecentFilesMenuPrivate(KRecentFilesMenu *q_ptr)
89  : q(q_ptr)
90 {}
91 
92 std::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 
99 void KRecentFilesMenuPrivate::recentFilesChanged() const
100 {
101  q->rebuildMenu();
102  Q_EMIT q->recentFilesChanged();
103 }
104 
105 KRecentFilesMenu::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 
122 KRecentFilesMenu::KRecentFilesMenu(QWidget *parent)
123  : KRecentFilesMenu(tr("Recent Files"), parent)
124 {
125 }
126 
127 KRecentFilesMenu::~KRecentFilesMenu()
128 {
129  writeToFile();
130  qDeleteAll(d->m_entries);
131  delete d->m_clearAction;
132  delete d->m_noEntriesAction;
133 }
134 
135 void 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 
159 void 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 
199 void 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 
219 void 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 
254 void 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"
int maximumItems() const
The maximum number of files this menu can hold.
void addUrl(const QUrl &url, const QString &name=QString())
Add URL to recent files list.
QFontMetrics fontMetrics() const const
QList< QUrl > recentFiles() const
List of URLs of recent files.
QList< QScreen * > screens()
void urlTriggered(const QUrl &url)
emitted when the user clicks on a file action.
QIcon fromTheme(const QString &name)
QAction * addSeparator()
int width() const const
QRect boundingRect(QChar ch) const const
void setGroup(const QString &group)
Specify a group for storing the URLs.
QString writableLocation(QStandardPaths::StandardLocation type)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QAction * addAction(const QString &text)
void reserve(int alloc)
QString group() const
The group the URLs are saved to/read from.
PreferLocalFile
bool isEmpty() const const
QString fileName(QUrl::ComponentFormattingOptions options) const const
QString toDisplayString(QUrl::FormattingOptions options) const const
void setMaximumItems(size_t maximumItems)
Set the maximum URL count.
void clearRecentFiles()
Clear recent files list.
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
ElideMiddle
void triggered(bool checked)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void setIcon(const QIcon &icon)
Set a custom icon.
void clear()
void removeUrl(const QUrl &url)
Remove a URL from the recent files list.
A menu that offers a set of recent files.
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
QString tr(const char *sourceText, const char *disambiguation, int n)
char * toString(const EngineQuery &query)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Sep 21 2023 04:03:41 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.