Krita Source Code Documentation
Loading...
Searching...
No Matches
krecentfilesaction.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
3 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
4 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
5 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
6 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
7 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
8 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
9 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
10 SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
11 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
12 SPDX-FileCopyrightText: 2022 Alvin Wong <alvin@alvinhc.com>
13
14 SPDX-License-Identifier: LGPL-2.0-only
15*/
16
17#include "krecentfilesaction.h"
19
20#include <QFile>
21#include <QGuiApplication>
22#include <QDir>
23#include <QMenu>
24#include <QScreen>
25#include <QProxyStyle>
26#include <QStandardItemModel>
27#include <QStyleFactory>
28#include <QActionGroup>
29
30#include <kconfig.h>
31#include <kconfiggroup.h>
32#include <klocalizedstring.h>
33
35
36class KRecentFilesIconProxyStyle : public QProxyStyle
37{
38public:
39 KRecentFilesIconProxyStyle(QStyle *style = nullptr)
40 : QProxyStyle(style)
41 {
42 }
43
44 int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const override
45 {
46 if (metric == QStyle::PM_SmallIconSize) {
47 return 48;
48 }
49 return QProxyStyle::pixelMetric(metric, option, widget);
50 }
51};
52
54 : KSelectAction(parent),
55 d_ptr(new KRecentFilesActionPrivate(this))
56{
58 d->init();
59}
60
61KRecentFilesAction::KRecentFilesAction(const QString &text, QObject *parent)
62 : KSelectAction(parent),
63 d_ptr(new KRecentFilesActionPrivate(this))
64{
66 d->init();
67
68 // Want to keep the ampersands
69 setText(text);
70}
71
72KRecentFilesAction::KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent)
73 : KSelectAction(parent),
74 d_ptr(new KRecentFilesActionPrivate(this))
75{
77 d->init();
78
79 setIcon(icon);
80 // Want to keep the ampersands
81 setText(text);
82}
83
85{
87 delete q->menu();
88 q->setMenu(new QMenu());
89 q->setToolBarMode(KSelectAction::MenuMode);
90 m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
91 m_noEntriesAction->setObjectName(QLatin1String("no_entries"));
92 m_noEntriesAction->setEnabled(false);
93 clearSeparator = q->menu()->addSeparator();
94 clearSeparator->setVisible(false);
95 clearSeparator->setObjectName(QLatin1String("separator"));
96 clearAction = q->menu()->addAction(i18n("Clear List"), q, SLOT(clearActionTriggered()));
97 clearAction->setObjectName(QLatin1String("clear_action"));
98 clearAction->setVisible(false);
99 q->setEnabled(false);
100#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
101 q->connect(q, SIGNAL(triggered(QAction*)), SLOT(_k_urlSelected(QAction*)));
102#else
103 q->connect(q, SIGNAL(actionTriggered(QAction*)), SLOT(_k_urlSelected(QAction*)));
104#endif
105 QString baseStyleName = q->menu()->style()->objectName();
106 if (baseStyleName != QLatin1String("windows")) {
107 // Force Fusion theme because other themes like Breeze doesn't
108 // work well with QProxyStyle, may result in small icons.
109 baseStyleName = QStringLiteral("fusion");
110 }
111 QStyle *baseStyle = QStyleFactory::create(baseStyleName);
112 QStyle *newStyle = new KRecentFilesIconProxyStyle(baseStyle);
113 newStyle->setParent(q->menu());
114 q->menu()->setStyle(newStyle);
115
116 q->connect(q->menu(),
117 SIGNAL(aboutToShow()),
118 SLOT(menuAboutToShow()));
119
121 SIGNAL(fileAdded(const QUrl &)),
122 SLOT(fileAdded(const QUrl &)));
124 SIGNAL(fileRemoved(const QUrl &)),
125 SLOT(fileRemoved(const QUrl &)));
127 SIGNAL(listRenewed()),
128 SLOT(listRenewed()));
129
130 // We have to manually trigger the initial load because
131 // KisRecentFilesManager is initialized earlier than this.
132 q->rebuildEntries();
133}
134
139
141{
143 Q_EMIT q->urlSelected(m_urls[action]);
144}
145
146void KRecentFilesActionPrivate::updateIcon(const QStandardItem *item)
147{
148 if (!item) {
149 return;
150 }
151 const QUrl url = item->data().toUrl();
152 if (!url.isValid()) {
153 return;
154 }
155 QAction *action = m_urls.key(url);
156 if (!action) {
157 return;
158 }
159 const QIcon icon = item->icon();
160 if (icon.isNull()) {
161 return;
162 }
163 action->setIcon(icon);
164 action->setIconVisibleInMenu(true);
165}
166
167static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)
168{
169 // Calculate 3/4 of screen geometry, we do not want
170 // action titles to be bigger than that
171 // Since we do not know in which screen we are going to show
172 // we choose the min of all the screens
173 int maxWidthForTitles = INT_MAX;
174 Q_FOREACH(const QScreen *screen, QGuiApplication::screens()) {
175 maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
176 }
177 const QFontMetrics fontMetrics = QFontMetrics(QFont());
178
179 QString title = nameValue + " [" + value + ']';
180 if (fontMetrics.boundingRect(title).width() > maxWidthForTitles) {
181 // If it does not fit, try to cut only the whole path, though if the
182 // name is too long (more than 3/4 of the whole text) we cut it a bit too
183 const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
184 const int nameWidth = fontMetrics.boundingRect(nameValue).width();
185 QString cutNameValue, cutValue;
186 if (nameWidth > nameValueMaxWidth) {
187 cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
188 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
189 } else {
190 cutNameValue = nameValue;
191 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
192 }
193 title = cutNameValue + " [" + cutValue + ']';
194 }
195 return title;
196}
197
198void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &/*name*/)
199{
201
202 menu()->insertAction(menu()->actions().value(0), action);
203 d->m_urls.insert(action, url);
204}
205
206QAction *KRecentFilesAction::removeAction(QAction *action)
207{
209 KSelectAction::removeAction(action);
210
211 d->m_urls.remove(action);
212
213 return action;
214}
215
216void KRecentFilesAction::setRecentFilesModel(const QStandardItemModel *model)
217{
219
220 if (d->m_recentFilesModel) {
221 disconnect(d->m_recentFilesModel, nullptr, this, nullptr);
222 }
223
224 d->m_recentFilesModel = model;
225 // Do not connect the signals or populate the icons now, because we want
226 // them to be lazy-loaded only when the menu is opened for the first time.
227 d->m_fileIconsPopulated = false;
228}
229
231{
233 d->updateIcon(item);
234}
235
236void KRecentFilesAction::modelRowsInserted(const QModelIndex &/*parent*/, int first, int last)
237{
239 for (int i = first; i <= last; i++) {
240 d->updateIcon(d->m_recentFilesModel->item(i));
241 }
242}
243
245{
247 if (!d->m_fileIconsPopulated) {
248 d->m_fileIconsPopulated = true;
249 connect(d->m_recentFilesModel, SIGNAL(itemChanged(QStandardItem *)),
250 SLOT(modelItemChanged(QStandardItem *)));
251 connect(d->m_recentFilesModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
252 SLOT(modelRowsInserted(const QModelIndex &, int, int)));
253 // Populate the file icons only on first showing the menu, so lazy
254 // loading actually works.
255 const int count = d->m_recentFilesModel->rowCount();
256 for (int i = 0; i < count; i++) {
257 d->updateIcon(d->m_recentFilesModel->item(i));
258 }
259 }
260}
261
266
268{
270 KSelectAction::clear();
271 d->m_urls.clear();
272 d->m_noEntriesAction->setVisible(true);
273 d->clearSeparator->setVisible(false);
274 d->clearAction->setVisible(false);
275 setEnabled(false);
276}
277
278void KRecentFilesAction::rebuildEntries()
279{
281
282 clearEntries();
283
285 if (items.count() > d->m_visibleItemsCount) {
286 items = items.mid(items.count() - d->m_visibleItemsCount);
287 }
288 bool thereAreEntries = false;
289 Q_FOREACH(const auto &item, items) {
290 QString value;
291 if (item.m_url.isLocalFile()) {
292 value = item.m_url.toLocalFile();
293#ifdef Q_OS_WIN
294 // Convert forward slashes to backslashes
295 value = QDir::toNativeSeparators(value);
296#endif
297 } else {
298 value = item.m_url.toDisplayString();
299 }
300 const QString nameValue = item.m_displayName;
301 const QString title = titleWithSensibleWidth(nameValue, value);
302 if (!value.isNull()) {
303 thereAreEntries = true;
304 QAction *action = new QAction(title);
305 action->setActionGroup(selectableActionGroup());
306 addAction(action, item.m_url, nameValue);
307 }
308 }
309 if (thereAreEntries) {
310 d->m_noEntriesAction->setVisible(false);
311 d->clearSeparator->setVisible(true);
312 d->clearAction->setVisible(true);
313 setEnabled(true);
314 }
315}
316
317void KRecentFilesAction::fileAdded(const QUrl &url)
318{
320 const QString name; // Dummy
321
322 if (d->m_visibleItemsCount <= 0) {
323 return;
324 }
325
326 // remove oldest item if already maxitems in list
327 if (selectableActionGroup()->actions().count() >= d->m_visibleItemsCount) {
328 // remove oldest added item
329 delete removeAction(selectableActionGroup()->actions().first());
330 }
331
332 const QString tmpName = name.isEmpty() ? url.fileName() : name;
333 const QString pathOrUrl(url.toDisplayString(QUrl::PreferLocalFile));
334
335#ifdef Q_OS_WIN
336 const QString file = url.isLocalFile() ? QDir::toNativeSeparators(pathOrUrl) : pathOrUrl;
337#else
338 const QString file = pathOrUrl;
339#endif
340
341 d->m_noEntriesAction->setVisible(false);
342 d->clearSeparator->setVisible(true);
343 d->clearAction->setVisible(true);
344 setEnabled(true);
345 // add file to list
346 const QString title = titleWithSensibleWidth(tmpName, file);
347 QAction *action = new QAction(title, selectableActionGroup());
348 addAction(action, url, tmpName);
349}
350
352{
354 for (QMap<QAction *, QUrl>::ConstIterator it = d->m_urls.constBegin(); it != d->m_urls.constEnd(); ++it) {
355 if (it.value() == url) {
356 delete removeAction(it.key());
357 return;
358 }
359 }
360}
361
363{
364 rebuildEntries();
365}
366
367#include "moc_krecentfilesaction.cpp"
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QMap< QAction *, QUrl > m_urls
void updateIcon(const QStandardItem *item)
Recent files action.
void setRecentFilesModel(const QStandardItemModel *model)
virtual void clearActionTriggered()
void modelItemChanged(QStandardItem *item)
void modelRowsInserted(const QModelIndex &parent, int first, int last)
void fileRemoved(const QUrl &url)
KRecentFilesAction(QObject *parent)
void addAction(QAction *action, const QUrl &url, const QString &name)
QAction * removeAction(QAction *action) override
KRecentFilesActionPrivate * d_ptr
void fileAdded(const QUrl &url)
int pixelMetric(PixelMetric metric, const QStyleOption *option=nullptr, const QWidget *widget=nullptr) const override
KRecentFilesIconProxyStyle(QStyle *style=nullptr)
QVector< KisRecentFilesEntry > recentFiles() const
static KisRecentFilesManager * instance()
static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)