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