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 | |