| 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 | |
| 75 | using namespace KDEPrivate; |
| 76 | |
| 77 | constexpr 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 | |
| 83 | class KPropertiesDialogPrivate |
| 84 | { |
| 85 | public: |
| 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(c: 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(sender: plugin, signal: &KPropertiesDialogPlugin::changed, context: plugin, slot: [plugin]() { |
| 108 | plugin->setDirty(); |
| 109 | }); |
| 110 | m_pages.push_back(x: 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 | |
| 141 | KPropertiesDialog::KPropertiesDialog(const KFileItem &item, QWidget *parent) |
| 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(t: item); |
| 149 | |
| 150 | d->m_singleUrl = item.url(); |
| 151 | Q_ASSERT(!d->m_singleUrl.isEmpty()); |
| 152 | |
| 153 | d->init(); |
| 154 | } |
| 155 | |
| 156 | KPropertiesDialog::KPropertiesDialog(const QString &title, QWidget *parent) |
| 157 | : KPageDialog(parent) |
| 158 | , d(new KPropertiesDialogPrivate(this)) |
| 159 | { |
| 160 | setWindowTitle(i18n("Properties for %1" , title)); |
| 161 | |
| 162 | d->init(); |
| 163 | } |
| 164 | |
| 165 | KPropertiesDialog::KPropertiesDialog(const KFileItemList &_items, QWidget *parent) |
| 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 | |
| 184 | KPropertiesDialog::KPropertiesDialog(const QUrl &_url, QWidget *parent) |
| 185 | : KPageDialog(parent) |
| 186 | , d(new KPropertiesDialogPrivate(this)) |
| 187 | { |
| 188 | d->m_singleUrl = _url.adjusted(options: QUrl::StripTrailingSlash); |
| 189 | |
| 190 | setWindowTitle(i18n("Properties for %1" , KIO::decodeFileName(d->m_singleUrl.fileName()))); |
| 191 | |
| 192 | KIO::StatJob *job = KIO::stat(url: d->m_singleUrl); |
| 193 | KJobWidgets::setWindow(job, widget: parent); |
| 194 | job->exec(); |
| 195 | KIO::UDSEntry entry = job->statResult(); |
| 196 | |
| 197 | d->m_items.append(t: KFileItem(entry, d->m_singleUrl)); |
| 198 | d->init(); |
| 199 | } |
| 200 | |
| 201 | KPropertiesDialog::KPropertiesDialog(const QList<QUrl> &urls, QWidget *parent) |
| 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(asize: urls.size()); |
| 216 | for (const QUrl &url : urls) { |
| 217 | KIO::StatJob *job = KIO::stat(url); |
| 218 | KJobWidgets::setWindow(job, widget: parent); |
| 219 | job->exec(); |
| 220 | KIO::UDSEntry entry = job->statResult(); |
| 221 | |
| 222 | d->m_items.append(t: KFileItem(entry, url)); |
| 223 | } |
| 224 | |
| 225 | d->init(); |
| 226 | } |
| 227 | |
| 228 | KPropertiesDialog::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(t: KFileItem(d->m_singleUrl)); |
| 241 | d->init(); |
| 242 | } |
| 243 | |
| 244 | #ifdef Q_OS_WIN |
| 245 | bool 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 | |
| 263 | bool 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 |
| 273 | KPropertiesDialog *dlg = new KPropertiesDialog(item, parent); |
| 274 | if (modal) { |
| 275 | dlg->exec(); |
| 276 | } else { |
| 277 | dlg->show(); |
| 278 | } |
| 279 | |
| 280 | return true; |
| 281 | } |
| 282 | |
| 283 | bool 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 |
| 290 | KPropertiesDialog *dlg = new KPropertiesDialog(_url, parent); |
| 291 | if (modal) { |
| 292 | dlg->exec(); |
| 293 | } else { |
| 294 | dlg->show(); |
| 295 | } |
| 296 | |
| 297 | return true; |
| 298 | } |
| 299 | |
| 300 | bool 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 | { |
| 307 | return KPropertiesDialog::showDialog(url: item.url(), parent, modal); |
| 308 | } else { |
| 309 | return KPropertiesDialog::showDialog(item: _items.first(), parent, modal); |
| 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 | |
| 321 | bool KPropertiesDialog::showDialog(const QList<QUrl> &urls, QWidget *parent, bool modal) |
| 322 | { |
| 323 | KPropertiesDialog *dlg = new KPropertiesDialog(urls, parent); |
| 324 | if (modal) { |
| 325 | dlg->exec(); |
| 326 | } else { |
| 327 | dlg->show(); |
| 328 | } |
| 329 | return true; |
| 330 | } |
| 331 | |
| 332 | void KPropertiesDialogPrivate::init() |
| 333 | { |
| 334 | q->setFaceType(KPageDialog::Tabbed); |
| 335 | |
| 336 | insertPages(); |
| 337 | // Ensure users can't make it so small where things break |
| 338 | q->setMinimumSize(q->sizeHint()); |
| 339 | } |
| 340 | |
| 341 | void KPropertiesDialog::showFileSharingPage() |
| 342 | { |
| 343 | if (d->fileSharePageItem) { |
| 344 | setCurrentPage(d->fileSharePageItem); |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | void KPropertiesDialog::setFileSharingPage(QWidget *page) |
| 349 | { |
| 350 | d->fileSharePageItem = addPage(widget: page, i18nc("@title:tab" , "Share" )); |
| 351 | } |
| 352 | |
| 353 | void KPropertiesDialog::setFileNameReadOnly(bool ro) |
| 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 | |
| 364 | KPropertiesDialog::~KPropertiesDialog() |
| 365 | { |
| 366 | } |
| 367 | |
| 368 | QUrl KPropertiesDialog::url() const |
| 369 | { |
| 370 | return d->m_singleUrl; |
| 371 | } |
| 372 | |
| 373 | KFileItem &KPropertiesDialog::item() |
| 374 | { |
| 375 | return d->m_items.first(); |
| 376 | } |
| 377 | |
| 378 | KFileItemList KPropertiesDialog::items() const |
| 379 | { |
| 380 | return d->m_items; |
| 381 | } |
| 382 | |
| 383 | QUrl KPropertiesDialog::currentDir() const |
| 384 | { |
| 385 | return d->m_currentDir; |
| 386 | } |
| 387 | |
| 388 | QString KPropertiesDialog::defaultName() const |
| 389 | { |
| 390 | return d->m_defaultName; |
| 391 | } |
| 392 | |
| 393 | bool KPropertiesDialog::canDisplay(const KFileItemList &_items) |
| 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 | |
| 404 | void KPropertiesDialog::accept() |
| 405 | { |
| 406 | d->m_aborted = false; |
| 407 | |
| 408 | auto acceptAndClose = [this]() { |
| 409 | Q_EMIT applied(); |
| 410 | Q_EMIT propertiesClosed(); |
| 411 | deleteLater(); // Somewhat like Qt::WA_DeleteOnClose would do. |
| 412 | KPageDialog::accept(); |
| 413 | }; |
| 414 | |
| 415 | const bool isAnyDirty = std::any_of(first: d->m_pages.cbegin(), last: d->m_pages.cend(), pred: [](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(sender: d->m_permissionsPropsPlugin, signal: &KFilePermissionsPropsPlugin::changesApplied, context: this, slot: [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(sender: d->m_filePropsPlugin, signal: &KFilePropsPlugin::changesApplied, context: this, slot: [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 | |
| 490 | void KPropertiesDialog::reject() |
| 491 | { |
| 492 | Q_EMIT canceled(); |
| 493 | Q_EMIT propertiesClosed(); |
| 494 | |
| 495 | deleteLater(); |
| 496 | KPageDialog::reject(); |
| 497 | } |
| 498 | |
| 499 | void KPropertiesDialogPrivate::insertPages() |
| 500 | { |
| 501 | if (m_items.isEmpty()) { |
| 502 | return; |
| 503 | } |
| 504 | |
| 505 | if (KFilePropsPlugin::supports(items: m_items)) { |
| 506 | m_filePropsPlugin = new KFilePropsPlugin(q); |
| 507 | insertPlugin(plugin: m_filePropsPlugin); |
| 508 | } |
| 509 | |
| 510 | if (KFilePermissionsPropsPlugin::supports(items: m_items)) { |
| 511 | m_permissionsPropsPlugin = new KFilePermissionsPropsPlugin(q); |
| 512 | insertPlugin(plugin: m_permissionsPropsPlugin); |
| 513 | } |
| 514 | |
| 515 | if (KChecksumsPlugin::supports(items: m_items)) { |
| 516 | KPropertiesDialogPlugin *p = new KChecksumsPlugin(q); |
| 517 | insertPlugin(plugin: p); |
| 518 | } |
| 519 | |
| 520 | if (KDesktopPropsPlugin::supports(items: m_items)) { |
| 521 | m_desktopPropsPlugin = new KDesktopPropsPlugin(q); |
| 522 | insertPlugin(plugin: m_desktopPropsPlugin); |
| 523 | } |
| 524 | |
| 525 | if (KUrlPropsPlugin::supports(items: m_items)) { |
| 526 | m_urlPropsPlugin = new KUrlPropsPlugin(q); |
| 527 | insertPlugin(plugin: 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" ), defaultValue: QStringList()); |
| 544 | if (!supportedProtocols.isEmpty()) { |
| 545 | const auto none = std::none_of(first: supportedProtocols.cbegin(), last: supportedProtocols.cend(), pred: [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: 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>(data: jsonMetadata, parent: q).plugin) { |
| 558 | insertPlugin(plugin); |
| 559 | } |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | void KPropertiesDialog::updateUrl(const QUrl &_newUrl) |
| 564 | { |
| 565 | Q_ASSERT(d->m_items.count() == 1); |
| 566 | // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl; |
| 567 | QUrl newUrl = _newUrl; |
| 568 | Q_EMIT saveAs(oldUrl: 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 | |
| 583 | void KPropertiesDialog::rename(const QString &_name) |
| 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(path: Utils::concatPaths(path1: newUrl.path(), path2: _name)); |
| 592 | } else { |
| 593 | // It's a directory, so strip the trailing slash first |
| 594 | newUrl = d->m_singleUrl.adjusted(options: QUrl::StripTrailingSlash); |
| 595 | // Now change the filename |
| 596 | newUrl = newUrl.adjusted(options: QUrl::RemoveFilename); // keep trailing slash |
| 597 | newUrl.setPath(path: Utils::concatPaths(path1: newUrl.path(), path2: _name)); |
| 598 | } |
| 599 | updateUrl(newUrl: newUrl); |
| 600 | } |
| 601 | |
| 602 | void KPropertiesDialog::abortApplying() |
| 603 | { |
| 604 | d->m_aborted = true; |
| 605 | } |
| 606 | |
| 607 | #include "moc_kpropertiesdialog.cpp" |
| 608 | |