KParts

readwritepart.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 1999 Simon Hausmann <[email protected]>
4  SPDX-FileCopyrightText: 1999-2005 David Faure <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "readwritepart.h"
10 #include "readwritepart_p.h"
11 
12 #include "kparts_logging.h"
13 
14 #include <KDirNotify>
15 #include <KIO/FileCopyJob>
16 #include <KJobWidgets>
17 #include <KLocalizedString>
18 #include <KMessageBox>
19 
20 #include <QApplication>
21 #include <QFileDialog>
22 #include <QTemporaryFile>
23 
24 #include <qplatformdefs.h>
25 
26 #ifdef Q_OS_WIN
27 #include <qt_windows.h> //CreateHardLink()
28 #endif
29 
30 using namespace KParts;
31 
33  : ReadOnlyPart(*new ReadWritePartPrivate(this), parent)
34 {
35 }
36 
38 {
39  // parent destructor will delete temp file
40  // we can't call our own closeUrl() here, because
41  // "cancel" wouldn't cancel anything. We have to assume
42  // the app called closeUrl() before destroying us.
43 }
44 
45 void ReadWritePart::setReadWrite(bool readwrite)
46 {
48 
49  // Perhaps we should check isModified here and issue a warning if true
50  d->m_bReadWrite = readwrite;
51 }
52 
53 void ReadWritePart::setModified(bool modified)
54 {
56 
57  // qDebug() << "setModified(" << (modified ? "true" : "false") << ")";
58  if (!d->m_bReadWrite && modified) {
59  qCCritical(KPARTSLOG) << "Can't set a read-only document to 'modified' !";
60  return;
61  }
62  d->m_bModified = modified;
63 }
64 
66 {
67  setModified(true);
68 }
69 
71 {
73 
74  if (!isReadWrite() || !isModified()) {
75  return true;
76  }
77 
78  QString docName = url().fileName();
79  if (docName.isEmpty()) {
80  docName = i18n("Untitled");
81  }
82 
83  QWidget *parentWidget = widget();
84  if (!parentWidget) {
85  parentWidget = QApplication::activeWindow();
86  }
87 
88  int res = KMessageBox::warningYesNoCancel(parentWidget,
89  i18n("The document \"%1\" has been modified.\n"
90  "Do you want to save your changes or discard them?",
91  docName),
92  i18n("Close Document"),
95 
96  bool abortClose = false;
97  bool handled = false;
98 
99  switch (res) {
100  case KMessageBox::Yes:
101  Q_EMIT sigQueryClose(&handled, &abortClose);
102  if (!handled) {
103  if (d->m_url.isEmpty()) {
104  QUrl url = QFileDialog::getSaveFileUrl(parentWidget);
105  if (url.isEmpty()) {
106  return false;
107  }
108 
109  saveAs(url);
110  } else {
111  save();
112  }
113  } else if (abortClose) {
114  return false;
115  }
116  return waitSaveComplete();
117  case KMessageBox::No:
118  return true;
119  default: // case KMessageBox::Cancel :
120  return false;
121  }
122 }
123 
125 {
126  abortLoad(); // just in case
127  if (isReadWrite() && isModified()) {
128  if (!queryClose()) {
129  return false;
130  }
131  }
132  // Not modified => ok and delete temp file.
133  return ReadOnlyPart::closeUrl();
134 }
135 
136 bool ReadWritePart::closeUrl(bool promptToSave)
137 {
138  return promptToSave ? closeUrl() : ReadOnlyPart::closeUrl();
139 }
140 
142 {
144 
145  d->m_saveOk = false;
146  if (d->m_file.isEmpty()) { // document was created empty
147  d->prepareSaving();
148  }
149  if (saveFile()) {
150  return saveToUrl();
151  } else {
153  }
154  return false;
155 }
156 
157 bool ReadWritePart::saveAs(const QUrl &url)
158 {
160 
161  if (!url.isValid()) {
162  qCCritical(KPARTSLOG) << "saveAs: Malformed URL" << url;
163  return false;
164  }
165  d->m_duringSaveAs = true;
166  d->m_originalURL = d->m_url;
167  d->m_originalFilePath = d->m_file;
168  d->m_url = url; // Store where to upload in saveToURL
169  d->prepareSaving();
170  bool result = save(); // Save local file and upload local file
171  if (result) {
172  if (d->m_originalURL != d->m_url) {
173  Q_EMIT urlChanged(d->m_url);
174  }
175 
176  Q_EMIT setWindowCaption(d->m_url.toDisplayString());
177  } else {
178  d->m_url = d->m_originalURL;
179  d->m_file = d->m_originalFilePath;
180  d->m_duringSaveAs = false;
181  d->m_originalURL = QUrl();
182  d->m_originalFilePath.clear();
183  }
184 
185  return result;
186 }
187 
188 // Set m_file correctly for m_url
189 void ReadWritePartPrivate::prepareSaving()
190 {
191  // Local file
192  if (m_url.isLocalFile()) {
193  if (m_bTemp) { // get rid of a possible temp file first
194  // (happens if previous url was remote)
195  QFile::remove(m_file);
196  m_bTemp = false;
197  }
198  m_file = m_url.toLocalFile();
199  } else {
200  // Remote file
201  // We haven't saved yet, or we did but locally - provide a temp file
202  if (m_file.isEmpty() || !m_bTemp) {
203  QTemporaryFile tempFile;
204  tempFile.setAutoRemove(false);
205  tempFile.open();
206  m_file = tempFile.fileName();
207  m_bTemp = true;
208  }
209  // otherwise, we already had a temp file
210  }
211 }
212 
213 static inline bool makeHardLink(const QString &src, const QString &dest)
214 {
215 #ifndef Q_OS_WIN
216  return ::link(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0;
217 #else
218  return CreateHardLinkW((LPCWSTR)dest.utf16(), (LPCWSTR)src.utf16(), nullptr) != 0;
219 #endif
220 }
221 
223 {
225 
226  if (d->m_url.isLocalFile()) {
227  setModified(false);
228  Q_EMIT completed();
229  // if m_url is a local file there won't be a temp file -> nothing to remove
230  Q_ASSERT(!d->m_bTemp);
231  d->m_saveOk = true;
232  d->m_duringSaveAs = false;
233  d->m_originalURL = QUrl();
234  d->m_originalFilePath.clear();
235  return true; // Nothing to do
236  } else {
237  if (d->m_uploadJob) {
238  QFile::remove(d->m_uploadJob->srcUrl().toLocalFile());
239  d->m_uploadJob->kill();
240  d->m_uploadJob = nullptr;
241  }
242  QTemporaryFile *tempFile = new QTemporaryFile();
243  tempFile->open();
244  QString uploadFile = tempFile->fileName();
245  delete tempFile;
246  QUrl uploadUrl = QUrl::fromLocalFile(uploadFile);
247  // Create hardlink
248  if (!makeHardLink(d->m_file, uploadFile)) {
249  // Uh oh, some error happened.
250  return false;
251  }
252  d->m_uploadJob = KIO::file_move(uploadUrl, d->m_url, -1, KIO::Overwrite);
253  KJobWidgets::setWindow(d->m_uploadJob, widget());
254 
255  connect(d->m_uploadJob, &KJob::result, this, [d](KJob *job) {
256  d->slotUploadFinished(job);
257  });
258 
259  return true;
260  }
261 }
262 
263 void ReadWritePartPrivate::slotUploadFinished(KJob *)
264 {
265  Q_Q(ReadWritePart);
266 
267  if (m_uploadJob->error()) {
268  QFile::remove(m_uploadJob->srcUrl().toLocalFile());
269  QString error = m_uploadJob->errorString();
270  m_uploadJob = nullptr;
271  if (m_duringSaveAs) {
272  q->setUrl(m_originalURL);
273  m_file = m_originalFilePath;
274  }
275  Q_EMIT q->canceled(error);
276  } else {
277 #ifndef Q_OS_ANDROID
278  ::org::kde::KDirNotify::emitFilesAdded(m_url.adjusted(QUrl::RemoveFilename));
279 #endif
280 
281  m_uploadJob = nullptr;
282  q->setModified(false);
283  Q_EMIT q->completed();
284  m_saveOk = true;
285  }
286  m_duringSaveAs = false;
287  m_originalURL = QUrl();
288  m_originalFilePath.clear();
289  if (m_waitForSave) {
290  m_eventLoop.quit();
291  }
292 }
293 
295 {
296  Q_D(const ReadWritePart);
297 
298  return d->m_bReadWrite;
299 }
300 
302 {
303  Q_D(const ReadWritePart);
304 
305  return d->m_bModified;
306 }
307 
309 {
311 
312  if (!d->m_uploadJob) {
313  return d->m_saveOk;
314  }
315 
316  d->m_waitForSave = true;
317 
318  d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
319 
320  d->m_waitForSave = false;
321 
322  return d->m_saveOk;
323 }
324 
325 #include "moc_readwritepart.cpp"
bool remove()
Q_EMITQ_EMIT
virtual bool closeUrl()
Called when closing the current URL (for example, a document), for instance when switching to another...
void result(KJob *job)
KJOBWIDGETS_EXPORT void setWindow(KJob *job, QWidget *widget)
QByteArray encodeName(const QString &fileName)
void setAutoRemove(bool b)
Base class for an "editor" part.
Definition: readwritepart.h:35
QWidget * activeWindow()
KGuiItem save()
void sigQueryClose(bool *handled, bool *abortClosing)
set handled to true, if you don't want the default handling set abortClosing to true,...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual QString fileName() const const override
bool isValid() const const
ButtonCode warningYesNoCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void setModified()
Call setModified() whenever the contents get modified.
QString i18n(const char *text, const TYPE &arg...)
bool isEmpty() const const
virtual bool queryClose()
If the document has been modified, ask the user to save changes.
RemoveFilename
virtual bool saveAs(const QUrl &url)
Save the file to a new location.
bool isEmpty() const const
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
QUrl fromLocalFile(const QString &localFile)
QString fileName(QUrl::ComponentFormattingOptions options) const const
virtual void setReadWrite(bool readwrite=true)
Changes the behavior of this part to readonly or readwrite.
virtual bool saveFile()=0
Save to a local file.
bool closeUrl() override
Called when closing the current url (e.g.
void completed()
Emit this when you have completed loading data.
ReadWritePart(QObject *parent=nullptr)
Constructor See parent constructor for instructions.
void urlChanged(const QUrl &url)
Emitted by the part when url() changes.
const ushort * utf16() const const
virtual bool saveToUrl()
Save the file.
Base class for any "viewer" part.
Definition: readonlypart.h:51
void canceled(const QString &errMsg)
Emit this if loading is canceled by the user or by an error.
virtual QWidget * widget()
Definition: part.cpp:75
~ReadWritePart() override
Destructor Applications using a ReadWritePart should make sure, before destroying it,...
virtual bool save()
Save the file in the location from which it was opened.
KGuiItem discard()
The KParts namespace,.
void setWindowCaption(const QString &caption)
Emitted by the part, to set the caption of the window(s) hosting this part.
Q_D(Todo)
bool waitSaveComplete()
Waits for any pending upload job to finish and returns whether the last save() action was successful.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 9 2022 03:51:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.