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 <QList>
55#include <QMimeData>
56#include <QMimeDatabase>
57#include <QRegularExpression>
58#include <QStandardPaths>
59#include <QUrl>
60
61#include <algorithm>
62#include <functional>
63#include <vector>
64
65#ifdef Q_OS_WIN
66#include <process.h>
67#include <qt_windows.h>
68#include <shellapi.h>
69#ifdef __GNUC__
70#warning TODO: port completely to win32
71#endif
72#endif
73
74using namespace KDEPrivate;
75
76constexpr mode_t KFilePermissionsPropsPlugin::fperm[3][4] = {
77 {S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID},
78 {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID},
79 {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX},
80};
81
82class KPropertiesDialogPrivate
83{
84public:
85 explicit KPropertiesDialogPrivate(KPropertiesDialog *qq)
86 : q(qq)
87 {
88 }
89 ~KPropertiesDialogPrivate()
90 {
91 // qDeleteAll deletes the pages in order, this prevents crashes when closing the dialog
92 qDeleteAll(m_pages);
93 }
94
95 /**
96 * Common initialization for all constructors
97 */
98 void init();
99 /**
100 * Inserts all pages in the dialog.
101 */
102 void insertPages();
103
104 void insertPlugin(KPropertiesDialogPlugin *plugin)
105 {
106 q->connect(plugin, &KPropertiesDialogPlugin::changed, plugin, [plugin]() {
107 plugin->setDirty();
108 });
109 m_pages.push_back(plugin);
110 }
111
112 KPropertiesDialog *const q;
113 bool m_aborted = false;
114 KPageWidgetItem *fileSharePageItem = nullptr;
115 KFilePropsPlugin *m_filePropsPlugin = nullptr;
116 KFilePermissionsPropsPlugin *m_permissionsPropsPlugin = nullptr;
117 KDesktopPropsPlugin *m_desktopPropsPlugin = nullptr;
118 KUrlPropsPlugin *m_urlPropsPlugin = nullptr;
119
120 /**
121 * The URL of the props dialog (when shown for only one file)
122 */
123 QUrl m_singleUrl;
124 /**
125 * List of items this props dialog is shown for
126 */
127 KFileItemList m_items;
128 /**
129 * For templates
130 */
131 QString m_defaultName;
132 QUrl m_currentDir;
133
134 /**
135 * List of all plugins inserted ( first one first )
136 */
137 std::vector<KPropertiesDialogPlugin *> m_pages;
138};
139
141 : KPageDialog(parent)
142 , d(new KPropertiesDialogPrivate(this))
143{
144 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(item.name())));
145
146 Q_ASSERT(!item.isNull());
147 d->m_items.append(item);
148
149 d->m_singleUrl = item.url();
150 Q_ASSERT(!d->m_singleUrl.isEmpty());
151
152 d->init();
153}
154
156 : KPageDialog(parent)
157 , d(new KPropertiesDialogPrivate(this))
158{
159 setWindowTitle(i18n("Properties for %1", title));
160
161 d->init();
162}
163
165 : KPageDialog(parent)
166 , d(new KPropertiesDialogPrivate(this))
167{
168 if (_items.count() > 1) {
169 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", _items.count()));
170 } else {
171 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_items.first().name())));
172 }
173
174 Q_ASSERT(!_items.isEmpty());
175 d->m_singleUrl = _items.first().url();
176 Q_ASSERT(!d->m_singleUrl.isEmpty());
177
178 d->m_items = _items;
179
180 d->init();
181}
182
184 : KPageDialog(parent)
185 , d(new KPropertiesDialogPrivate(this))
186{
187 d->m_singleUrl = _url.adjusted(QUrl::StripTrailingSlash);
188
189 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(d->m_singleUrl.fileName())));
190
191 KIO::StatJob *job = KIO::stat(d->m_singleUrl);
193 job->exec();
194 KIO::UDSEntry entry = job->statResult();
195
196 d->m_items.append(KFileItem(entry, d->m_singleUrl));
197 d->init();
198}
199
201 : KPageDialog(parent)
202 , d(new KPropertiesDialogPrivate(this))
203{
204 if (urls.count() > 1) {
205 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", urls.count()));
206 } else {
207 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(urls.first().fileName())));
208 }
209
210 Q_ASSERT(!urls.isEmpty());
211 d->m_singleUrl = urls.first();
212 Q_ASSERT(!d->m_singleUrl.isEmpty());
213
214 d->m_items.reserve(urls.size());
215 for (const QUrl &url : urls) {
216 KIO::StatJob *job = KIO::stat(url);
218 job->exec();
219 KIO::UDSEntry entry = job->statResult();
220
221 d->m_items.append(KFileItem(entry, url));
222 }
223
224 d->init();
225}
226
227KPropertiesDialog::KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent)
228 : KPageDialog(parent)
229 , d(new KPropertiesDialogPrivate(this))
230{
231 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_tempUrl.fileName())));
232
233 d->m_singleUrl = _tempUrl;
234 d->m_defaultName = _defaultName;
235 d->m_currentDir = _currentDir;
236 Q_ASSERT(!d->m_singleUrl.isEmpty());
237
238 // Create the KFileItem for the _template_ file, in order to read from it.
239 d->m_items.append(KFileItem(d->m_singleUrl));
240 d->init();
241}
242
243#ifdef Q_OS_WIN
244bool showWin32FilePropertyDialog(const QString &fileName)
245{
246 QString path_ = QDir::toNativeSeparators(QFileInfo(fileName).absoluteFilePath());
247
248 SHELLEXECUTEINFOW execInfo;
249
250 memset(&execInfo, 0, sizeof(execInfo));
251 execInfo.cbSize = sizeof(execInfo);
252 execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
253
254 const QString verb(QLatin1String("properties"));
255 execInfo.lpVerb = (LPCWSTR)verb.utf16();
256 execInfo.lpFile = (LPCWSTR)path_.utf16();
257
258 return ShellExecuteExW(&execInfo);
259}
260#endif
261
262bool KPropertiesDialog::showDialog(const KFileItem &item, QWidget *parent, bool modal)
263{
264 // TODO: do we really want to show the win32 property dialog?
265 // This means we lose metainfo, support for .desktop files, etc. (DF)
266#ifdef Q_OS_WIN
267 QString localPath = item.localPath();
268 if (!localPath.isEmpty()) {
269 return showWin32FilePropertyDialog(localPath);
270 }
271#endif
273 if (modal) {
274 dlg->exec();
275 } else {
276 dlg->show();
277 }
278
279 return true;
280}
281
282bool KPropertiesDialog::showDialog(const QUrl &_url, QWidget *parent, bool modal)
283{
284#ifdef Q_OS_WIN
285 if (_url.isLocalFile()) {
286 return showWin32FilePropertyDialog(_url.toLocalFile());
287 }
288#endif
290 if (modal) {
291 dlg->exec();
292 } else {
293 dlg->show();
294 }
295
296 return true;
297}
298
299bool KPropertiesDialog::showDialog(const KFileItemList &_items, QWidget *parent, bool modal)
300{
301 if (_items.count() == 1) {
302 const KFileItem &item = _items.first();
303 if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a worker
304 // Let's stat to get more info on the file
305 {
307 } else {
309 }
310 }
311 KPropertiesDialog *dlg = new KPropertiesDialog(_items, parent);
312 if (modal) {
313 dlg->exec();
314 } else {
315 dlg->show();
316 }
317 return true;
318}
319
320bool KPropertiesDialog::showDialog(const QList<QUrl> &urls, QWidget *parent, bool modal)
321{
323 if (modal) {
324 dlg->exec();
325 } else {
326 dlg->show();
327 }
328 return true;
329}
330
331void KPropertiesDialogPrivate::init()
332{
334
335 insertPages();
336}
337
339{
340 if (d->fileSharePageItem) {
341 setCurrentPage(d->fileSharePageItem);
342 }
343}
344
346{
347 d->fileSharePageItem = addPage(page, i18nc("@title:tab", "Share"));
348}
349
351{
352 if (d->m_filePropsPlugin) {
353 d->m_filePropsPlugin->setFileNameReadOnly(ro);
354 }
355
356 if (d->m_urlPropsPlugin) {
357 d->m_urlPropsPlugin->setFileNameReadOnly(ro);
358 }
359}
360
364
366{
367 return d->m_singleUrl;
368}
369
371{
372 return d->m_items.first();
373}
374
376{
377 return d->m_items;
378}
379
381{
382 return d->m_currentDir;
383}
384
386{
387 return d->m_defaultName;
388}
389
391{
392 // TODO: cache the result of those calls. Currently we parse .desktop files far too many times
393 /* clang-format off */
394 return KFilePropsPlugin::supports(_items)
395 || KFilePermissionsPropsPlugin::supports(_items)
396 || KDesktopPropsPlugin::supports(_items)
397 || KUrlPropsPlugin::supports(_items);
398 /* clang-format on */
399}
400
402{
403 d->m_aborted = false;
404
405 auto acceptAndClose = [this]() {
406 Q_EMIT applied();
408 deleteLater(); // Somewhat like Qt::WA_DeleteOnClose would do.
410 };
411
412 const bool isAnyDirty = std::any_of(d->m_pages.cbegin(), d->m_pages.cend(), [](const KPropertiesDialogPlugin *page) {
413 return page->isDirty();
414 });
415
416 if (!isAnyDirty) { // No point going further
417 acceptAndClose();
418 return;
419 }
420
421 // If any page is dirty, then set the main one (KFilePropsPlugin) as
422 // dirty too. This is what makes it possible to save changes to a global
423 // desktop file into a local one. In other cases, it doesn't hurt.
424 if (d->m_filePropsPlugin) {
425 d->m_filePropsPlugin->setDirty(true);
426 }
427
428 // Changes are applied in the following order:
429 // - KFilePropsPlugin changes, this is because in case of renaming an item or saving changes
430 // of a template or a .desktop file, the renaming or copying respectively, must be finished
431 // first, before applying the rest of the changes
432 // - KFilePermissionsPropsPlugin changes, e.g. if the item was read-only and was changed to
433 // read/write, this must be applied first for other changes to work
434 // - The rest of the changes from the other plugins/tabs
435 // - KFilePropsPlugin::postApplyChanges()
436
437 auto applyOtherChanges = [this, acceptAndClose]() {
438 Q_ASSERT(!d->m_filePropsPlugin->isDirty());
439 Q_ASSERT(!d->m_permissionsPropsPlugin->isDirty());
440
441 // Apply the changes for the rest of the plugins
442 for (auto *page : d->m_pages) {
443 if (d->m_aborted) {
444 break;
445 }
446
447 if (page->isDirty()) {
448 // qDebug() << "applying changes for " << page->metaObject()->className();
449 page->applyChanges();
450 }
451 /* else {
452 qDebug() << "skipping page " << page->metaObject()->className();
453 } */
454 }
455
456 if (!d->m_aborted && d->m_filePropsPlugin) {
457 d->m_filePropsPlugin->postApplyChanges();
458 }
459
460 if (!d->m_aborted) {
461 acceptAndClose();
462 } // Else, keep dialog open for user to fix the problem.
463 };
464
465 auto applyPermissionsChanges = [this, applyOtherChanges]() {
466 connect(d->m_permissionsPropsPlugin, &KFilePermissionsPropsPlugin::changesApplied, this, [applyOtherChanges]() {
467 applyOtherChanges();
468 });
469
470 d->m_permissionsPropsPlugin->applyChanges();
471 };
472
473 if (d->m_filePropsPlugin && d->m_filePropsPlugin->isDirty()) {
474 // changesApplied() is _not_ emitted if applying the changes was aborted
475 connect(d->m_filePropsPlugin, &KFilePropsPlugin::changesApplied, this, [this, applyPermissionsChanges, applyOtherChanges]() {
476 if (d->m_permissionsPropsPlugin && d->m_permissionsPropsPlugin->isDirty()) {
477 applyPermissionsChanges();
478 } else {
479 applyOtherChanges();
480 }
481 });
482
483 d->m_filePropsPlugin->applyChanges();
484 }
485}
486
495
496void KPropertiesDialogPrivate::insertPages()
497{
498 if (m_items.isEmpty()) {
499 return;
500 }
501
502 if (KFilePropsPlugin::supports(m_items)) {
503 m_filePropsPlugin = new KFilePropsPlugin(q);
504 insertPlugin(m_filePropsPlugin);
505 }
506
507 if (KFilePermissionsPropsPlugin::supports(m_items)) {
508 m_permissionsPropsPlugin = new KFilePermissionsPropsPlugin(q);
509 insertPlugin(m_permissionsPropsPlugin);
510 }
511
512 if (KChecksumsPlugin::supports(m_items)) {
513 KPropertiesDialogPlugin *p = new KChecksumsPlugin(q);
514 insertPlugin(p);
515 }
516
517 if (KDesktopPropsPlugin::supports(m_items)) {
518 m_desktopPropsPlugin = new KDesktopPropsPlugin(q);
519 insertPlugin(m_desktopPropsPlugin);
520 }
521
522 if (KUrlPropsPlugin::supports(m_items)) {
523 m_urlPropsPlugin = new KUrlPropsPlugin(q);
524 insertPlugin(m_urlPropsPlugin);
525 }
526
527 if (m_items.count() != 1) {
528 return;
529 }
530
531 const KFileItem item = m_items.first();
532 const QString mimetype = item.mimetype();
533
534 if (mimetype.isEmpty()) {
535 return;
536 }
537
538 const auto scheme = item.url().scheme();
539 const auto filter = [mimetype, scheme](const KPluginMetaData &metaData) {
540 const auto supportedProtocols = metaData.value(QStringLiteral("X-KDE-Protocols"), QStringList());
541 if (!supportedProtocols.isEmpty()) {
542 const auto none = std::none_of(supportedProtocols.cbegin(), supportedProtocols.cend(), [scheme](const auto &protocol) {
543 return !protocol.isEmpty() && protocol == scheme;
544 });
545 if (none) {
546 return false;
547 }
548 }
549
550 return metaData.mimeTypes().isEmpty() || metaData.supportsMimeType(mimetype);
551 };
552 const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/propertiesdialog"), filter);
553 for (const auto &jsonMetadata : jsonPlugins) {
554 if (auto plugin = KPluginFactory::instantiatePlugin<KPropertiesDialogPlugin>(jsonMetadata, q).plugin) {
555 insertPlugin(plugin);
556 }
557 }
558}
559
561{
562 Q_ASSERT(d->m_items.count() == 1);
563 // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl;
564 QUrl newUrl = _newUrl;
565 Q_EMIT saveAs(d->m_singleUrl, newUrl);
566 // qDebug() << "KPropertiesDialog::updateUrl (post)" << newUrl;
567
568 d->m_singleUrl = newUrl;
569 d->m_items.first().setUrl(newUrl);
570 Q_ASSERT(!d->m_singleUrl.isEmpty());
571 // If we have an Desktop page, set it dirty, so that a full file is saved locally
572 // Same for a URL page (because of the Name= hack)
573 if (d->m_urlPropsPlugin) {
574 d->m_urlPropsPlugin->setDirty();
575 } else if (d->m_desktopPropsPlugin) {
576 d->m_desktopPropsPlugin->setDirty();
577 }
578}
579
581{
582 Q_ASSERT(d->m_items.count() == 1);
583 // qDebug() << "KPropertiesDialog::rename " << _name;
584 QUrl newUrl;
585 // if we're creating from a template : use currentdir
586 if (!d->m_currentDir.isEmpty()) {
587 newUrl = d->m_currentDir;
588 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name));
589 } else {
590 // It's a directory, so strip the trailing slash first
591 newUrl = d->m_singleUrl.adjusted(QUrl::StripTrailingSlash);
592 // Now change the filename
593 newUrl = newUrl.adjusted(QUrl::RemoveFilename); // keep trailing slash
594 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name));
595 }
596 updateUrl(newUrl);
597}
598
600{
601 d->m_aborted = true;
602}
603
604#include "moc_kpropertiesdialog.cpp"
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:630
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.
Definition statjob.h:26
const UDSEntry & statResult() const
Result of the stat operation.
Definition statjob.cpp:80
Universal Directory Service.
Definition udsentry.h:78
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 i18np(const char *singular, const char *plural, const TYPE &arg...)
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:120
void setWindow(QObject *job, QWidget *widget)
virtual void accept()
virtual int exec()
virtual void reject()
QString toNativeSeparators(const QString &pathName)
int count(const T &value) const const
T & first()
bool isEmpty() const const
int size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
QObject * parent() const const
bool isEmpty() const const
const ushort * utf16() const const
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
StripTrailingSlash
QUrl adjusted(QUrl::FormattingOptions options) const const
QString fileName(QUrl::ComponentFormattingOptions options) const const
bool isLocalFile() const const
QString path(QUrl::ComponentFormattingOptions options) const const
QString scheme() const const
void setPath(const QString &path, QUrl::ParsingMode mode)
QString toLocalFile() const const
void show()
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:45:23 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.