1 /*
2 * SPDX-FileCopyrightText: 2011-2021 Laurent Montel <montel@kde.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 #include "renamefiledialog.h"
7 #include <KIO/StatJob>
8 #include <KJobWidgets>
9 #include <KLocalizedString>
10 #include <KMessageBox>
11 #include <KStandardGuiItem>
12 #include <QLineEdit>
13 #include <QPushButton>
14 #include <kseparator.h>
15
16 #include <QCheckBox>
17 #include <QFileInfo>
18 #include <QHBoxLayout>
19 #include <QLabel>
20 #include <QVBoxLayout>
21
22 using namespace PimCommon;
23
24 class Q_DECL_HIDDEN PimCommon::RenameFileDialog::RenameFileDialogPrivate
25 {
26 public:
RenameFileDialogPrivate(const QUrl & _url,RenameFileDialog * qq)27 RenameFileDialogPrivate(const QUrl &_url, RenameFileDialog *qq)
28 : url(_url)
29 , q(qq)
30 {
31 }
32
33 Q_REQUIRED_RESULT QString suggestName(const QUrl &baseURL, const QString &oldName);
34
35 const QUrl url;
36 QCheckBox *applyAll = nullptr;
37 QPushButton *renameBtn = nullptr;
38 QPushButton *suggestNewNameBtn = nullptr;
39 QLineEdit *nameEdit = nullptr;
40 RenameFileDialog *const q;
41 };
42
suggestName(const QUrl & baseURL,const QString & oldName)43 QString PimCommon::RenameFileDialog::RenameFileDialogPrivate::suggestName(const QUrl &baseURL, const QString &oldName)
44 {
45 QString dotSuffix;
46 QString suggestedName;
47 QString basename = oldName;
48 const QChar spacer(QLatin1Char(' '));
49
50 // ignore dots at the beginning, that way "..aFile.tar.gz" will become "..aFile 1.tar.gz" instead of " 1..aFile.tar.gz"
51 int index = basename.indexOf(QLatin1Char('.'));
52 int continuous = 0;
53 while (continuous == index) {
54 index = basename.indexOf(QLatin1Char('.'), index + 1);
55 ++continuous;
56 }
57
58 if (index != -1) {
59 dotSuffix = basename.mid(index);
60 basename.truncate(index);
61 }
62
63 const int pos = basename.lastIndexOf(spacer);
64
65 if (pos != -1) {
66 const QString tmp = basename.mid(pos + 1);
67 bool ok;
68 const int number = tmp.toInt(&ok);
69
70 if (!ok) { // ok there is no number
71 suggestedName = basename + spacer + QLatin1Char('1') + dotSuffix;
72 } else {
73 // yes there's already a number behind the spacer so increment it by one
74 basename.replace(pos + 1, tmp.length(), QString::number(number + 1));
75 suggestedName = basename + dotSuffix;
76 }
77 } else { // no spacer yet
78 suggestedName = basename + spacer + QLatin1Char('1') + dotSuffix;
79 }
80
81 // Check if suggested name already exists
82 bool exists = false;
83 // TODO: network transparency. However, using NetAccess from a modal dialog
84 // could be a problem, no? (given that it uses a modal widget itself....)
85 if (baseURL.isLocalFile()) {
86 exists = QFileInfo::exists(baseURL.toLocalFile() + QLatin1Char('/') + suggestedName);
87 }
88
89 if (!exists) {
90 return suggestedName;
91 } else { // already exists -> recurse
92 return suggestName(baseURL, suggestedName);
93 }
94 }
95
RenameFileDialog(const QUrl & url,bool multiFiles,QWidget * parent)96 RenameFileDialog::RenameFileDialog(const QUrl &url, bool multiFiles, QWidget *parent)
97 : QDialog(parent)
98 , d(new RenameFileDialogPrivate(url, this))
99 {
100 setWindowTitle(i18nc("@title:window", "File Already Exists"));
101 auto pLayout = new QVBoxLayout(this);
102
103 auto label = new QLabel(xi18n("A file named <filename>%1</filename> already exists. Do you want to overwrite it?", url.fileName()), this);
104 pLayout->addWidget(label);
105
106 auto renameLayout = new QHBoxLayout();
107 pLayout->addLayout(renameLayout);
108
109 d->nameEdit = new QLineEdit(this);
110 renameLayout->addWidget(d->nameEdit);
111 d->nameEdit->setClearButtonEnabled(true);
112 d->nameEdit->setText(url.fileName());
113 d->suggestNewNameBtn = new QPushButton(i18n("Suggest New &Name"), this);
114 renameLayout->addWidget(d->suggestNewNameBtn);
115 connect(d->suggestNewNameBtn, &QPushButton::clicked, this, &RenameFileDialog::slotSuggestNewNamePressed);
116
117 auto overWrite = new QPushButton(this);
118 KStandardGuiItem::assign(overWrite, KStandardGuiItem::Overwrite);
119 connect(overWrite, &QPushButton::clicked, this, &RenameFileDialog::slotOverwritePressed);
120
121 auto ignore = new QPushButton(i18n("&Ignore"), this);
122 connect(ignore, &QPushButton::clicked, this, &RenameFileDialog::slotIgnorePressed);
123
124 d->renameBtn = new QPushButton(i18n("&Rename"), this);
125 connect(d->renameBtn, &QPushButton::clicked, this, &RenameFileDialog::slotRenamePressed);
126
127 auto separator = new KSeparator(this);
128 pLayout->addWidget(separator);
129
130 auto layout = new QHBoxLayout();
131 pLayout->addLayout(layout);
132
133 if (multiFiles) {
134 d->applyAll = new QCheckBox(i18n("Appl&y to All"), this);
135 connect(d->applyAll, &QCheckBox::clicked, this, &RenameFileDialog::slotApplyAllPressed);
136 layout->addWidget(d->applyAll);
137 slotApplyAllPressed();
138 }
139 layout->addWidget(d->renameBtn);
140 layout->addWidget(overWrite);
141 layout->addWidget(ignore);
142 }
143
144 RenameFileDialog::~RenameFileDialog() = default;
145
slotOverwritePressed()146 void RenameFileDialog::slotOverwritePressed()
147 {
148 if (d->applyAll && d->applyAll->isChecked()) {
149 done(RENAMEFILE_OVERWRITEALL);
150 } else {
151 done(RENAMEFILE_OVERWRITE);
152 }
153 }
154
slotIgnorePressed()155 void RenameFileDialog::slotIgnorePressed()
156 {
157 if (d->applyAll && d->applyAll->isChecked()) {
158 done(RENAMEFILE_IGNOREALL);
159 } else {
160 done(RENAMEFILE_IGNORE);
161 }
162 }
163
slotRenamePressed()164 void RenameFileDialog::slotRenamePressed()
165 {
166 if (d->nameEdit->text().isEmpty()) {
167 return;
168 }
169
170 bool fileExists = false;
171 if (newName().isLocalFile()) {
172 fileExists = QFile::exists(newName().path());
173 } else {
174 auto job = KIO::statDetails(newName(), KIO::StatJob::DestinationSide, KIO::StatBasic);
175 KJobWidgets::setWindow(job, this);
176 fileExists = job->exec();
177 }
178
179 if (fileExists) {
180 KMessageBox::error(this, i18n("This filename \"%1\" already exists.", newName().toDisplayString(QUrl::PreferLocalFile)), i18n("File already exists"));
181 return;
182 }
183 done(RENAMEFILE_RENAME);
184 }
185
slotApplyAllPressed()186 void RenameFileDialog::slotApplyAllPressed()
187 {
188 const bool enabled(!d->applyAll->isChecked());
189 d->nameEdit->setEnabled(enabled);
190 d->suggestNewNameBtn->setEnabled(enabled);
191 d->renameBtn->setEnabled(enabled);
192 }
193
slotSuggestNewNamePressed()194 void RenameFileDialog::slotSuggestNewNamePressed()
195 {
196 if (d->nameEdit->text().isEmpty()) {
197 return;
198 }
199
200 auto destDirectory = d->url.adjusted(QUrl::RemoveFilename);
201 d->nameEdit->setText(d->suggestName(destDirectory, d->nameEdit->text()));
202 }
203
newName() const204 QUrl RenameFileDialog::newName() const
205 {
206 const QString fileName = d->nameEdit->text();
207
208 auto newDest = d->url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
209 newDest.setPath(newDest.path() + QLatin1Char('/') + KIO::encodeFileName(fileName));
210
211 return newDest;
212 }
213