KIO

kpropertiesdialog.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
4 SPDX-FileCopyrightText: 1999, 2000 Preston Brown <pbrown@kde.org>
5 SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
6 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
7 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
8 SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
9 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
10
11 SPDX-License-Identifier: LGPL-2.0-or-later
12*/
13
14/*
15 * kpropertiesdialog.cpp
16 * View/Edit Properties of files, locally or remotely
17 *
18 * some FilePermissionsPropsPlugin-changes by
19 * Henner Zeller <zeller@think.de>
20 * some layout management by
21 * Bertrand Leconte <B.Leconte@mail.dotcom.fr>
22 * the rest of the layout management, bug fixes, adaptation to libkio,
23 * template feature by
24 * David Faure <faure@kde.org>
25 * More layout, cleanups, and fixes by
26 * Preston Brown <pbrown@kde.org>
27 * Plugin capability, cleanups and port to KDialog by
28 * Simon Hausmann <hausmann@kde.org>
29 * KDesktopPropsPlugin by
30 * Waldo Bastian <bastian@kde.org>
31 */
32
33#include "kpropertiesdialog.h"
34#include "../utils_p.h"
35#include "kio_widgets_debug.h"
36#include "kpropertiesdialogbuiltin_p.h"
37
38#include <config-kiowidgets.h>
39
40#include <kacl.h>
41#include <kio/global.h>
42#include <kio/statjob.h>
43#include <kioglobal_p.h>
44
45#include <KJobWidgets>
46#include <KLocalizedString>
47#include <KPluginFactory>
48#include <KPluginMetaData>
49
50#include <qplatformdefs.h>
51
52#include <QDebug>
53#include <QDir>
54#include <QLayout>
55#include <QList>
56#include <QMimeData>
57#include <QMimeDatabase>
58#include <QRegularExpression>
59#include <QStandardPaths>
60#include <QUrl>
61
62#include <algorithm>
63#include <functional>
64#include <vector>
65
66#ifdef Q_OS_WIN
67#include <process.h>
68#include <qt_windows.h>
69#include <shellapi.h>
70#ifdef __GNUC__
71#warning TODO: port completely to win32
72#endif
73#endif
74
75using namespace KDEPrivate;
76
77constexpr mode_t KFilePermissionsPropsPlugin::fperm[3][4] = {
78 {S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID},
79 {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID},
80 {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX},
81};
82
83class KPropertiesDialogPrivate
84{
85public:
86 explicit KPropertiesDialogPrivate(KPropertiesDialog *qq)
87 : q(qq)
88 {
89 }
90 ~KPropertiesDialogPrivate()
91 {
92 // qDeleteAll deletes the pages in order, this prevents crashes when closing the dialog
93 qDeleteAll(m_pages);
94 }
95
96 /**
97 * Common initialization for all constructors
98 */
99 void init();
100 /**
101 * Inserts all pages in the dialog.
102 */
103 void insertPages();
104
105 void insertPlugin(KPropertiesDialogPlugin *plugin)
106 {
107 q->connect(plugin, &KPropertiesDialogPlugin::changed, plugin, [plugin]() {
108 plugin->setDirty();
109 });
110 m_pages.push_back(plugin);
111 }
112
113 KPropertiesDialog *const q;
114 bool m_aborted = false;
115 KPageWidgetItem *fileSharePageItem = nullptr;
116 KFilePropsPlugin *m_filePropsPlugin = nullptr;
117 KFilePermissionsPropsPlugin *m_permissionsPropsPlugin = nullptr;
118 KDesktopPropsPlugin *m_desktopPropsPlugin = nullptr;
119 KUrlPropsPlugin *m_urlPropsPlugin = nullptr;
120
121 /**
122 * The URL of the props dialog (when shown for only one file)
123 */
124 QUrl m_singleUrl;
125 /**
126 * List of items this props dialog is shown for
127 */
128 KFileItemList m_items;
129 /**
130 * For templates
131 */
132 QString m_defaultName;
133 QUrl m_currentDir;
134
135 /**
136 * List of all plugins inserted ( first one first )
137 */
138 std::vector<KPropertiesDialogPlugin *> m_pages;
139};
140
142 : KPageDialog(parent)
143 , d(new KPropertiesDialogPrivate(this))
144{
145 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(item.name())));
146
147 Q_ASSERT(!item.isNull());
148 d->m_items.append(item);
149
150 d->m_singleUrl = item.url();
151 Q_ASSERT(!d->m_singleUrl.isEmpty());
152
153 d->init();
154}
155
157 : KPageDialog(parent)
158 , d(new KPropertiesDialogPrivate(this))
159{
160 setWindowTitle(i18n("Properties for %1", title));
161
162 d->init();
163}
164
166 : KPageDialog(parent)
167 , d(new KPropertiesDialogPrivate(this))
168{
169 if (_items.count() > 1) {
170 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", _items.count()));
171 } else {
172 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_items.first().name())));
173 }
174
175 Q_ASSERT(!_items.isEmpty());
176 d->m_singleUrl = _items.first().url();
177 Q_ASSERT(!d->m_singleUrl.isEmpty());
178
179 d->m_items = _items;
180
181 d->init();
182}
183
185 : KPageDialog(parent)
186 , d(new KPropertiesDialogPrivate(this))
187{
188 d->m_singleUrl = _url.adjusted(QUrl::StripTrailingSlash);
189
190 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(d->m_singleUrl.fileName())));
191
192 KIO::StatJob *job = KIO::stat(d->m_singleUrl);
194 job->exec();
195 KIO::UDSEntry entry = job->statResult();
196
197 d->m_items.append(KFileItem(entry, d->m_singleUrl));
198 d->init();
199}
200
202 : KPageDialog(parent)
203 , d(new KPropertiesDialogPrivate(this))
204{
205 if (urls.count() > 1) {
206 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", urls.count()));
207 } else {
208 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(urls.first().fileName())));
209 }
210
211 Q_ASSERT(!urls.isEmpty());
212 d->m_singleUrl = urls.first();
213 Q_ASSERT(!d->m_singleUrl.isEmpty());
214
215 d->m_items.reserve(urls.size());
216 for (const QUrl &url : urls) {
217 KIO::StatJob *job = KIO::stat(url);
219 job->exec();
220 KIO::UDSEntry entry = job->statResult();
221
222 d->m_items.append(KFileItem(entry, url));
223 }
224
225 d->init();
226}
227
228KPropertiesDialog::KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent)
229 : KPageDialog(parent)
230 , d(new KPropertiesDialogPrivate(this))
231{
232 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_tempUrl.fileName())));
233
234 d->m_singleUrl = _tempUrl;
235 d->m_defaultName = _defaultName;
236 d->m_currentDir = _currentDir;
237 Q_ASSERT(!d->m_singleUrl.isEmpty());
238
239 // Create the KFileItem for the _template_ file, in order to read from it.
240 d->m_items.append(KFileItem(d->m_singleUrl));
241 d->init();
242}
243
244#ifdef Q_OS_WIN
245bool showWin32FilePropertyDialog(const QString &fileName)
246{
247 QString path_ = QDir::toNativeSeparators(QFileInfo(fileName).absoluteFilePath());
248
249 SHELLEXECUTEINFOW execInfo;
250
251 memset(&execInfo, 0, sizeof(execInfo));
252 execInfo.cbSize = sizeof(execInfo);
253 execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
254
255 const QString verb(QLatin1String("properties"));
256 execInfo.lpVerb = (LPCWSTR)verb.utf16();
257 execInfo.lpFile = (LPCWSTR)path_.utf16();
258
259 return ShellExecuteExW(&execInfo);
260}
261#endif
262
263bool KPropertiesDialog::showDialog(const KFileItem &item, QWidget *parent, bool modal)
264{
265 // TODO: do we really want to show the win32 property dialog?
266 // This means we lose metainfo, support for .desktop files, etc. (DF)
267#ifdef Q_OS_WIN
268 QString localPath = item.localPath();
269 if (!localPath.isEmpty()) {
270 return showWin32FilePropertyDialog(localPath);
271 }
272#endif
274 if (modal) {
275 dlg->exec();
276 } else {
277 dlg->show();
278 }
279
280 return true;
281}
282
283bool KPropertiesDialog::showDialog(const QUrl &_url, QWidget *parent, bool modal)
284{
285#ifdef Q_OS_WIN
286 if (_url.isLocalFile()) {
287 return showWin32FilePropertyDialog(_url.toLocalFile());
288 }
289#endif
291 if (modal) {
292 dlg->exec();
293 } else {
294 dlg->show();
295 }
296
297 return true;
298}
299
300bool KPropertiesDialog::showDialog(const KFileItemList &_items, QWidget *parent, bool modal)
301{
302 if (_items.count() == 1) {
303 const KFileItem &item = _items.first();
304 if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a worker
305 // Let's stat to get more info on the file
306 {
308 } else {
310 }
311 }
312 KPropertiesDialog *dlg = new KPropertiesDialog(_items, parent);
313 if (modal) {
314 dlg->exec();
315 } else {
316 dlg->show();
317 }
318 return true;
319}
320
321bool KPropertiesDialog::showDialog(const QList<QUrl> &urls, QWidget *parent, bool modal)
322{
324 if (modal) {
325 dlg->exec();
326 } else {
327 dlg->show();
328 }
329 return true;
330}
331
332void KPropertiesDialogPrivate::init()
333{
335
336 insertPages();
337 // Ensure users can't make it so small where things break
338 q->setMinimumSize(q->sizeHint());
339}
340
342{
343 if (d->fileSharePageItem) {
344 setCurrentPage(d->fileSharePageItem);
345 }
346}
347
349{
350 d->fileSharePageItem = addPage(page, i18nc("@title:tab", "Share"));
351}
352
354{
355 if (d->m_filePropsPlugin) {
356 d->m_filePropsPlugin->setFileNameReadOnly(ro);
357 }
358
359 if (d->m_urlPropsPlugin) {
360 d->m_urlPropsPlugin->setFileNameReadOnly(ro);
361 }
362}
363
367
369{
370 return d->m_singleUrl;
371}
372
374{
375 return d->m_items.first();
376}
377
379{
380 return d->m_items;
381}
382
384{
385 return d->m_currentDir;
386}
387
389{
390 return d->m_defaultName;
391}
392
394{
395 // TODO: cache the result of those calls. Currently we parse .desktop files far too many times
396 /* clang-format off */
397 return KFilePropsPlugin::supports(_items)
398 || KFilePermissionsPropsPlugin::supports(_items)
399 || KDesktopPropsPlugin::supports(_items)
400 || KUrlPropsPlugin::supports(_items);
401 /* clang-format on */
402}
403
405{
406 d->m_aborted = false;
407
408 auto acceptAndClose = [this]() {
409 Q_EMIT applied();
411 deleteLater(); // Somewhat like Qt::WA_DeleteOnClose would do.
413 };
414
415 const bool isAnyDirty = std::any_of(d->m_pages.cbegin(), d->m_pages.cend(), [](const KPropertiesDialogPlugin *page) {
416 return page->isDirty();
417 });
418
419 if (!isAnyDirty) { // No point going further
420 acceptAndClose();
421 return;
422 }
423
424 // If any page is dirty, then set the main one (KFilePropsPlugin) as
425 // dirty too. This is what makes it possible to save changes to a global
426 // desktop file into a local one. In other cases, it doesn't hurt.
427 if (d->m_filePropsPlugin) {
428 d->m_filePropsPlugin->setDirty(true);
429 }
430
431 // Changes are applied in the following order:
432 // - KFilePropsPlugin changes, this is because in case of renaming an item or saving changes
433 // of a template or a .desktop file, the renaming or copying respectively, must be finished
434 // first, before applying the rest of the changes
435 // - KFilePermissionsPropsPlugin changes, e.g. if the item was read-only and was changed to
436 // read/write, this must be applied first for other changes to work
437 // - The rest of the changes from the other plugins/tabs
438 // - KFilePropsPlugin::postApplyChanges()
439
440 auto applyOtherChanges = [this, acceptAndClose]() {
441 Q_ASSERT(!d->m_filePropsPlugin->isDirty());
442 Q_ASSERT(!d->m_permissionsPropsPlugin->isDirty());
443
444 // Apply the changes for the rest of the plugins
445 for (auto *page : d->m_pages) {
446 if (d->m_aborted) {
447 break;
448 }
449
450 if (page->isDirty()) {
451 // qDebug() << "applying changes for " << page->metaObject()->className();
452 page->applyChanges();
453 }
454 /* else {
455 qDebug() << "skipping page " << page->metaObject()->className();
456 } */
457 }
458
459 if (!d->m_aborted && d->m_filePropsPlugin) {
460 d->m_filePropsPlugin->postApplyChanges();
461 }
462
463 if (!d->m_aborted) {
464 acceptAndClose();
465 } // Else, keep dialog open for user to fix the problem.
466 };
467
468 auto applyPermissionsChanges = [this, applyOtherChanges]() {
469 connect(d->m_permissionsPropsPlugin, &KFilePermissionsPropsPlugin::changesApplied, this, [applyOtherChanges]() {
470 applyOtherChanges();
471 });
472
473 d->m_permissionsPropsPlugin->applyChanges();
474 };
475
476 if (d->m_filePropsPlugin && d->m_filePropsPlugin->isDirty()) {
477 // changesApplied() is _not_ emitted if applying the changes was aborted
478 connect(d->m_filePropsPlugin, &KFilePropsPlugin::changesApplied, this, [this, applyPermissionsChanges, applyOtherChanges]() {
479 if (d->m_permissionsPropsPlugin && d->m_permissionsPropsPlugin->isDirty()) {
480 applyPermissionsChanges();
481 } else {
482 applyOtherChanges();
483 }
484 });
485
486 d->m_filePropsPlugin->applyChanges();
487 }
488}
489
498
499void KPropertiesDialogPrivate::insertPages()
500{
501 if (m_items.isEmpty()) {
502 return;
503 }
504
505 if (KFilePropsPlugin::supports(m_items)) {
506 m_filePropsPlugin = new KFilePropsPlugin(q);
507 insertPlugin(m_filePropsPlugin);
508 }
509
510 if (KFilePermissionsPropsPlugin::supports(m_items)) {
511 m_permissionsPropsPlugin = new KFilePermissionsPropsPlugin(q);
512 insertPlugin(m_permissionsPropsPlugin);
513 }
514
515 if (KChecksumsPlugin::supports(m_items)) {
516 KPropertiesDialogPlugin *p = new KChecksumsPlugin(q);
517 insertPlugin(p);
518 }
519
520 if (KDesktopPropsPlugin::supports(m_items)) {
521 m_desktopPropsPlugin = new KDesktopPropsPlugin(q);
522 insertPlugin(m_desktopPropsPlugin);
523 }
524
525 if (KUrlPropsPlugin::supports(m_items)) {
526 m_urlPropsPlugin = new KUrlPropsPlugin(q);
527 insertPlugin(m_urlPropsPlugin);
528 }
529
530 if (m_items.count() != 1) {
531 return;
532 }
533
534 const KFileItem item = m_items.first();
535 const QString mimetype = item.mimetype();
536
537 if (mimetype.isEmpty()) {
538 return;
539 }
540
541 const auto scheme = item.url().scheme();
542 const auto filter = [mimetype, scheme](const KPluginMetaData &metaData) {
543 const auto supportedProtocols = metaData.value(QStringLiteral("X-KDE-Protocols"), QStringList());
544 if (!supportedProtocols.isEmpty()) {
545 const auto none = std::none_of(supportedProtocols.cbegin(), supportedProtocols.cend(), [scheme](const auto &protocol) {
546 return !protocol.isEmpty() && protocol == scheme;
547 });
548 if (none) {
549 return false;
550 }
551 }
552
553 return metaData.mimeTypes().isEmpty() || metaData.supportsMimeType(mimetype);
554 };
555 const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/propertiesdialog"), filter);
556 for (const auto &jsonMetadata : jsonPlugins) {
557 if (auto plugin = KPluginFactory::instantiatePlugin<KPropertiesDialogPlugin>(jsonMetadata, q).plugin) {
558 insertPlugin(plugin);
559 }
560 }
561}
562
564{
565 Q_ASSERT(d->m_items.count() == 1);
566 // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl;
567 QUrl newUrl = _newUrl;
568 Q_EMIT saveAs(d->m_singleUrl, newUrl);
569 // qDebug() << "KPropertiesDialog::updateUrl (post)" << newUrl;
570
571 d->m_singleUrl = newUrl;
572 d->m_items.first().setUrl(newUrl);
573 Q_ASSERT(!d->m_singleUrl.isEmpty());
574 // If we have an Desktop page, set it dirty, so that a full file is saved locally
575 // Same for a URL page (because of the Name= hack)
576 if (d->m_urlPropsPlugin) {
577 d->m_urlPropsPlugin->setDirty();
578 } else if (d->m_desktopPropsPlugin) {
579 d->m_desktopPropsPlugin->setDirty();
580 }
581}
582
584{
585 Q_ASSERT(d->m_items.count() == 1);
586 // qDebug() << "KPropertiesDialog::rename " << _name;
587 QUrl newUrl;
588 // if we're creating from a template : use currentdir
589 if (!d->m_currentDir.isEmpty()) {
590 newUrl = d->m_currentDir;
591 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name));
592 } else {
593 // It's a directory, so strip the trailing slash first
594 newUrl = d->m_singleUrl.adjusted(QUrl::StripTrailingSlash);
595 // Now change the filename
596 newUrl = newUrl.adjusted(QUrl::RemoveFilename); // keep trailing slash
597 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name));
598 }
599 updateUrl(newUrl);
600}
601
603{
604 d->m_aborted = true;
605}
606
607#include "moc_kpropertiesdialog.cpp"
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:632
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
bool isNull() const
Return true if default-constructed.
KIO::UDSEntry entry() const
Returns the UDS entry.
A KIO job that retrieves information about a file or directory.
const UDSEntry & statResult() const
Result of the stat operation.
Definition statjob.cpp:80
Universal Directory Service.
int count() const
count fields
Definition udsentry.cpp:415
bool exec()
void setCurrentPage(KPageWidgetItem *item)
void setFaceType(FaceType faceType)
void addPage(KPageWidgetItem *item)
static QList< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter={}, KPluginMetaDataOptions options={})
A Plugin in the Properties dialog This is an abstract class.
virtual void applyChanges()
Applies all changes to the file.
void changed()
Emit this signal when the user changed anything in the plugin's tabs.
The main properties dialog class.
void applied()
This signal is emitted when the properties changes are applied (for example, with the OK button)
void updateUrl(const QUrl &newUrl)
Updates the item URL (either called by rename or because a global apps/mimelnk desktop file is being ...
static bool showDialog(const KFileItem &item, QWidget *parent=nullptr, bool modal=true)
Immediately displays a Properties dialog using constructor with the same parameters.
void saveAs(const QUrl &oldUrl, QUrl &newUrl)
Emitted before changes to oldUrl are saved as newUrl.
static bool canDisplay(const KFileItemList &_items)
Determine whether there are any property pages available for the given file items.
void setFileNameReadOnly(bool ro)
Call this to make the filename lineedit readonly, to prevent the user from renaming the file.
void accept() override
Called when the user presses 'Ok'.
void showFileSharingPage()
Shows the page that was previously set by setFileSharingPage(), or does nothing if no page was set ye...
KFileItemList items() const
void setFileSharingPage(QWidget *page)
Sets the file sharing page.
void abortApplying()
To abort applying changes.
void propertiesClosed()
This signal is emitted when the Properties Dialog is closed (for example, with OK or Cancel buttons)
QUrl url() const
The URL of the file that has its properties being displayed.
~KPropertiesDialog() override
Cleans up the properties dialog and frees any associated resources, including the dialog itself.
QString defaultName() const
If the dialog is being built from a template, this method returns the default name.
QUrl currentDir() const
If the dialog is being built from a template, this method returns the current directory.
KPropertiesDialog(const KFileItem &item, QWidget *parent=nullptr)
Brings up a Properties dialog, as shown above.
void rename(const QString &_name)
Renames the item to the specified name.
void reject() override
Called when the user presses 'Cancel' or Esc.
void canceled()
This signal is emitted when the properties changes are aborted (for example, with the Cancel button)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
Find MIME type for one file or directory.
KIOCORE_EXPORT QString decodeFileName(const QString &str)
Decodes (from the filename to the text displayed) This doesn't do anything anymore,...
Definition global.cpp:118
void setWindow(QObject *job, QWidget *widget)
virtual void accept()
virtual int exec()
virtual void reject()
virtual QSize sizeHint() const const override
QString toNativeSeparators(const QString &pathName)
qsizetype count() const const
T & first()
bool isEmpty() const const
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
bool isEmpty() const const
const ushort * utf16() const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
StripTrailingSlash
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
QString scheme() const const
void setPath(const QString &path, ParsingMode mode)
QString toLocalFile() const const
void setMinimumSize(const QSize &)
void show()
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.