/* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "part.h" #include "event.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KParts; namespace KParts { class PartBasePrivate { public: Q_DECLARE_PUBLIC(PartBase) PartBasePrivate(PartBase *q): q_ptr(q) { } PartBase *q_ptr; }; class PartPrivate: public PartBasePrivate { public: Q_DECLARE_PUBLIC(Part) PartPrivate(Part *q) : PartBasePrivate(q), m_iconLoader(nullptr), m_bSelectable(true), m_autoDeleteWidget(true), m_autoDeletePart(true) { } ~PartPrivate() { } KIconLoader* m_iconLoader; bool m_bSelectable; bool m_autoDeleteWidget; bool m_autoDeletePart; QPointer m_widget; }; } PartBase::PartBase() : d_ptr(new PartBasePrivate(this)) { } PartBase::PartBase(PartBasePrivate &dd) : d_ptr(&dd) { } PartBase::~PartBase() { delete d_ptr; } void PartBase::setComponentData(const KComponentData &componentData) { KXMLGUIClient::setComponentData(componentData); KGlobal::locale()->insertCatalog(componentData.catalogName()); // install 'instancename'data resource type KGlobal::dirs()->addResourceType(QString(componentData.componentName() + "data").toUtf8(), "data", componentData.componentName()); } Part::Part( QObject *parent ) : QObject( parent ), PartBase( *new PartPrivate(this) ) { } Part::Part(PartPrivate &dd, QObject *parent) : QObject( parent ), PartBase( dd ) { } Part::~Part() { Q_D(Part); // kDebug() << this; if ( d->m_widget ) { // We need to disconnect first to avoid calling slotWidgetDestroyed() ! disconnect( d->m_widget, 0, this, 0 ); } if ( d->m_widget && d->m_autoDeleteWidget ) { kDebug() << "deleting widget" << d->m_widget << d->m_widget->objectName(); delete static_cast(d->m_widget); } delete d->m_iconLoader; } void Part::embed( QWidget * parentWidget ) { if ( widget() ) { widget()->setParent( parentWidget, 0 ); widget()->setGeometry( 0, 0, widget()->width(), widget()->height() ); widget()->show(); } } QWidget *Part::widget() { Q_D(Part); return d->m_widget; } void Part::setAutoDeleteWidget(bool autoDeleteWidget) { Q_D(Part); d->m_autoDeleteWidget = autoDeleteWidget; if (d->m_widget) { if (autoDeleteWidget) { connect( d->m_widget, SIGNAL(destroyed()), this, SLOT(slotWidgetDestroyed()), Qt::UniqueConnection ); } else { disconnect(d->m_widget, 0, this, 0); } } } void Part::setAutoDeletePart(bool autoDeletePart) { Q_D(Part); d->m_autoDeletePart = autoDeletePart; } KIconLoader* Part::iconLoader() { Q_D(Part); if (!d->m_iconLoader) { Q_ASSERT(componentData().isValid()); d->m_iconLoader = new KIconLoader(componentData()); } return d->m_iconLoader; } Part* Part::hitTest(QWidget *widget, const QPoint &) { Q_D(Part); if ((QWidget *)d->m_widget != widget) { return 0; } return this; } void Part::setWidget( QWidget *widget ) { Q_D(Part); d->m_widget = widget; connect( d->m_widget, SIGNAL(destroyed()), this, SLOT(slotWidgetDestroyed()), Qt::UniqueConnection ); } void Part::setSelectable(bool selectable) { Q_D(Part); d->m_bSelectable = selectable; } bool Part::isSelectable() const { Q_D(const Part); return d->m_bSelectable; } void Part::customEvent(QEvent *ev) { if (GUIActivateEvent::test(ev)) { guiActivateEvent(static_cast(ev)); return; } QObject::customEvent(ev); } void Part::guiActivateEvent(GUIActivateEvent *) { } QWidget *Part::hostContainer(const QString &containerName) { if (!factory()) { return nullptr; } return factory()->container(containerName, this); } void Part::slotWidgetDestroyed() { Q_D(Part); d->m_widget = nullptr; if (d->m_autoDeletePart) { kDebug() << "deleting part" << objectName(); delete this; // ouch, this should probably be deleteLater() } } ////////////////////////////////////////////////// namespace KParts { class ReadOnlyPartPrivate: public PartPrivate { public: Q_DECLARE_PUBLIC(ReadOnlyPart) ReadOnlyPartPrivate(ReadOnlyPart *q): PartPrivate(q), m_job(0), m_statJob(0), m_uploadJob(0), m_showProgressInfo(true), m_saveOk(false), m_waitForSave(false), m_duringSaveAs(false), m_bTemp(false), m_bAutoDetectedMime(false), m_url(0), m_file("") { } void _k_slotJobFinished(KJob *job); void _k_slotStatJobFinished(KJob *job); bool openLocalFile(); void openRemoteFile(); KIO::FileCopyJob* m_job; KIO::StatJob* m_statJob; KIO::FileCopyJob* m_uploadJob; KUrl m_originalURL; // for saveAs QString m_originalFilePath; // for saveAs bool m_showProgressInfo; bool m_saveOk; bool m_waitForSave; bool m_duringSaveAs; /** * If @p true, @p m_file is a temporary file that needs to be deleted later. */ bool m_bTemp; // whether the mimetype in the arguments was detected by the part itself bool m_bAutoDetectedMime; /** * Remote (or local) url - the one displayed to the user. */ KUrl m_url; /** * Local file - the only one the part implementation should deal with. */ QString m_file; OpenUrlArguments m_arguments; }; class ReadWritePartPrivate: public ReadOnlyPartPrivate { public: Q_DECLARE_PUBLIC(ReadWritePart) ReadWritePartPrivate(ReadWritePart *q) : ReadOnlyPartPrivate(q) { m_bModified = false; m_bReadWrite = true; m_bClosing = false; } void _k_slotUploadFinished(KJob *job); void prepareSaving(); bool m_bModified; bool m_bReadWrite; bool m_bClosing; QEventLoop m_eventLoop; }; } ReadOnlyPart::ReadOnlyPart(QObject *parent) : Part(*new ReadOnlyPartPrivate(this), parent) { } ReadOnlyPart::ReadOnlyPart(ReadOnlyPartPrivate &dd, QObject *parent) : Part(dd, parent) { } ReadOnlyPart::~ReadOnlyPart() { ReadOnlyPart::closeUrl(); } KUrl ReadOnlyPart::url() const { Q_D(const ReadOnlyPart); return d->m_url; } void ReadOnlyPart::setUrl(const KUrl &url) { Q_D(ReadOnlyPart); emit urlAboutToChange(); d->m_url = url; emit urlChanged(url); } QString ReadOnlyPart::localFilePath() const { Q_D(const ReadOnlyPart); return d->m_file; } void ReadOnlyPart::setLocalFilePath(const QString &localFilePath) { Q_D(ReadOnlyPart); d->m_file = localFilePath; } void ReadOnlyPart::setProgressInfoEnabled(bool show) { Q_D(ReadOnlyPart); d->m_showProgressInfo = show; } bool ReadOnlyPart::isProgressInfoEnabled() const { Q_D(const ReadOnlyPart); return d->m_showProgressInfo; } bool ReadOnlyPart::openUrl(const KUrl &url) { Q_D(ReadOnlyPart); if (!url.isValid()) { return false; } if (d->m_bAutoDetectedMime) { d->m_arguments.setMimeType(QString()); d->m_bAutoDetectedMime = false; } OpenUrlArguments args = d->m_arguments; if (!closeUrl()) { return false; } d->m_arguments = args; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); return d->openLocalFile(); } d->openRemoteFile(); return true; } bool ReadOnlyPart::openFile() { kWarning() << "Default implementation of ReadOnlyPart::openFile called!" << metaObject()->className() << "should reimplement either openUrl or openFile."; return false; } bool ReadOnlyPartPrivate::openLocalFile() { Q_Q(ReadOnlyPart); emit q->started(0); m_bTemp = false; // set the mimetype only if it was not already set (for example, by the host application) if (m_arguments.mimeType().isEmpty()) { // get the mimetype of the file using findByUrl() to avoid another string -> url conversion KMimeType::Ptr mime = KMimeType::findByUrl(m_url); if (mime) { m_arguments.setMimeType(mime->name()); m_bAutoDetectedMime = true; } } const bool ret = q->openFile(); if (ret) { emit q->setWindowCaption(m_url.prettyUrl()); emit q->completed(); } else { emit q->canceled(QString()); } return ret; } void ReadOnlyPartPrivate::openRemoteFile() { Q_Q(ReadOnlyPart); m_bTemp = true; m_file = KTemporaryFile::urlPath(m_url); KUrl destURL; destURL.setPath(m_file); KIO::JobFlags flags = (m_showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo); flags |= KIO::Overwrite; m_job = KIO::file_copy(m_url, destURL, 0600, flags); m_job->ui()->setWindow(q->widget() ? q->widget()->window() : 0); emit q->started(m_job); QObject::connect( m_job, SIGNAL(result(KJob*)), q, SLOT(_k_slotJobFinished(KJob*)) ); } void ReadOnlyPart::abortLoad() { Q_D(ReadOnlyPart); if ( d->m_statJob ) { // kDebug() << "Aborting job" << d->m_statJob; d->m_statJob->kill(); d->m_statJob = nullptr; } if ( d->m_job ) { // kDebug() << "Aborting job" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } bool ReadOnlyPart::closeUrl() { Q_D(ReadOnlyPart); abortLoad(); //just in case d->m_arguments = KParts::OpenUrlArguments(); if (d->m_bTemp) { QFile::remove(d->m_file); d->m_bTemp = false; } // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void ReadOnlyPartPrivate::_k_slotJobFinished(KJob *job) { Q_Q(ReadOnlyPart); Q_ASSERT(job == m_job); m_job = nullptr; if (job->error() != KJob::NoError) { emit q->canceled(job->errorString()); } else { if (q->openFile()) { emit q->setWindowCaption(m_url.prettyUrl()); emit q->completed(); } else { emit q->canceled(QString()); } } } void ReadOnlyPart::guiActivateEvent(GUIActivateEvent *event) { Q_D(ReadOnlyPart); if (event->activated()) { if (!d->m_url.isEmpty()) { kDebug() << d->m_url; emit setWindowCaption(d->m_url.prettyUrl()); } else { emit setWindowCaption(""); } } } void KParts::ReadOnlyPart::setArguments(const OpenUrlArguments& arguments) { Q_D(ReadOnlyPart); d->m_arguments = arguments; d->m_bAutoDetectedMime = arguments.mimeType().isEmpty(); } OpenUrlArguments KParts::ReadOnlyPart::arguments() const { Q_D(const ReadOnlyPart); return d->m_arguments; } ////////////////////////////////////////////////// ReadWritePart::ReadWritePart(QObject *parent) : ReadOnlyPart(*new ReadWritePartPrivate(this), parent) { } ReadWritePart::~ReadWritePart() { // parent destructor will delete temp file // we can't call our own closeUrl() here, because // "cancel" wouldn't cancel anything. We have to assume // the app called closeUrl() before destroying us. } void ReadWritePart::setReadWrite(bool readwrite) { Q_D(ReadWritePart); // Perhaps we should check isModified here and issue a warning if true d->m_bReadWrite = readwrite; } void ReadWritePart::setModified(bool modified) { Q_D(ReadWritePart); kDebug() << "setModified(" << (modified ? "true" : "false") << ")"; if (!d->m_bReadWrite && modified) { kError() << "Can't set a read-only document to 'modified' !"; return; } d->m_bModified = modified; } void ReadWritePart::setModified() { setModified(true); } bool ReadWritePart::queryClose() { Q_D(ReadWritePart); if (!isReadWrite() || !isModified()) { return true; } QString docName = url().fileName(); if (docName.isEmpty()) { docName = i18n("Untitled"); } QWidget *parentWidget = widget(); if (!parentWidget) { parentWidget = QApplication::activeWindow(); } int res = KMessageBox::warningYesNoCancel( parentWidget, i18n( "The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?", docName ), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard() ); bool abortClose = false; bool handled = false; switch(res) { case KMessageBox::Yes: { sigQueryClose(&handled, &abortClose); if (!handled) { if (d->m_url.isEmpty()) { KUrl url = KFileDialog::getSaveUrl(KUrl(), QString(), parentWidget); if (url.isEmpty()) { return false; } saveAs(url); } else { save(); } } else if (abortClose) { return false; } return waitSaveComplete(); } case KMessageBox::No: { return true; } // case KMessageBox::Cancel: default: { return false; } } } bool ReadWritePart::closeUrl() { abortLoad(); //just in case if (isReadWrite() && isModified()) { if (!queryClose()) { return false; } } // Not modified => ok and delete temp file. return ReadOnlyPart::closeUrl(); } bool ReadWritePart::closeUrl(bool promptToSave) { return (promptToSave ? closeUrl() : ReadOnlyPart::closeUrl()); } bool ReadWritePart::save() { Q_D(ReadWritePart); d->m_saveOk = false; if (d->m_file.isEmpty()) { // document was created empty d->prepareSaving(); } if (saveFile()) { return saveToUrl(); } else { emit canceled(QString()); } return false; } bool ReadWritePart::saveAs(const KUrl &kurl) { Q_D(ReadWritePart); if (!kurl.isValid()) { kError() << "saveAs: Malformed URL " << kurl.url(); return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = kurl; // Store where to upload in saveToURL d->prepareSaving(); bool result = save(); // Save local file and upload local file if (result) { emit urlChanged(d->m_url); emit setWindowCaption(d->m_url.prettyUrl()); } else { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = KUrl(); d->m_originalFilePath.clear(); } return result; } // Set m_file correctly for m_url void ReadWritePartPrivate::prepareSaving() { // Local file if (m_url.isLocalFile()) { if (m_bTemp) { // get rid of a possible temp file first (happens if previous url was remote) QFile::remove(m_file); m_bTemp = false; } m_file = m_url.toLocalFile(); } else { // Remote file not saved yet, or it was but locally - provide a temp file if (m_file.isEmpty() || !m_bTemp) { m_file = KTemporaryFile::filePath(); m_bTemp = true; } // otherwise, already had a temp file } } bool ReadWritePart::saveToUrl() { Q_D(ReadWritePart); if (d->m_url.isLocalFile()) { setModified(false); emit completed(); // if m_url is a local file there won't be a temp file -> nothing to remove Q_ASSERT(!d->m_bTemp); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = KUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } else { if (d->m_uploadJob) { QFile::remove(d->m_uploadJob->srcUrl().toLocalFile()); d->m_uploadJob->kill(); d->m_uploadJob = 0; } QString uploadFile = KTemporaryFile::filePath(); KUrl uploadUrl; uploadUrl.setPath(uploadFile); // Create hardlink if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0) { // Uh oh, some error happened. return false; } d->m_uploadJob = KIO::file_move(uploadUrl, d->m_url, -1, KIO::Overwrite); d->m_uploadJob->ui()->setWindow( widget() ? widget()->window() : 0 ); connect( d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*)) ); return true; } } void ReadWritePartPrivate::_k_slotUploadFinished(KJob *) { Q_Q(ReadWritePart); if (m_uploadJob->error() != KJob::NoError) { QFile::remove(m_uploadJob->srcUrl().toLocalFile()); QString error = m_uploadJob->errorString(); m_uploadJob = nullptr; if (m_duringSaveAs) { q->setUrl(m_originalURL); m_file = m_originalFilePath; } emit q->canceled(error); } else { KUrl dirUrl(m_url); dirUrl.setPath( dirUrl.directory() ); ::org::kde::KDirNotify::emitFilesAdded(dirUrl.url()); m_uploadJob = 0; q->setModified(false); emit q->completed(); m_saveOk = true; } m_duringSaveAs = false; m_originalURL = KUrl(); m_originalFilePath.clear(); if (m_waitForSave) { m_eventLoop.quit(); } } bool ReadWritePart::isReadWrite() const { Q_D(const ReadWritePart); return d->m_bReadWrite; } bool ReadWritePart::isModified() const { Q_D(const ReadWritePart); return d->m_bModified; } bool ReadWritePart::waitSaveComplete() { Q_D(ReadWritePart); if (!d->m_uploadJob) { return d->m_saveOk; } d->m_waitForSave = true; d->m_eventLoop.exec(); d->m_waitForSave = false; return d->m_saveOk; } //// class KParts::OpenUrlArgumentsPrivate : public QSharedData { public: OpenUrlArgumentsPrivate() : reload(false), actionRequestedByUser(true), xOffset(0), yOffset(0) { } bool reload; bool actionRequestedByUser; int xOffset; int yOffset; QString mimeType; QMap metaData; }; KParts::OpenUrlArguments::OpenUrlArguments() : d(new OpenUrlArgumentsPrivate) { } KParts::OpenUrlArguments::OpenUrlArguments(const OpenUrlArguments &other) : d(other.d) { } KParts::OpenUrlArguments & KParts::OpenUrlArguments::operator=( const OpenUrlArguments &other) { d = other.d; return *this; } KParts::OpenUrlArguments::~OpenUrlArguments() { } bool KParts::OpenUrlArguments::reload() const { return d->reload; } void KParts::OpenUrlArguments::setReload(bool b) { d->reload = b; } int KParts::OpenUrlArguments::xOffset() const { return d->xOffset; } void KParts::OpenUrlArguments::setXOffset(int x) { d->xOffset = x; } int KParts::OpenUrlArguments::yOffset() const { return d->yOffset; } void KParts::OpenUrlArguments::setYOffset(int y) { d->yOffset = y; } QString KParts::OpenUrlArguments::mimeType() const { return d->mimeType; } void KParts::OpenUrlArguments::setMimeType(const QString& mime) { d->mimeType = mime; } QMap& KParts::OpenUrlArguments::metaData() { return d->metaData; } const QMap & KParts::OpenUrlArguments::metaData() const { return d->metaData; } bool KParts::OpenUrlArguments::actionRequestedByUser() const { return d->actionRequestedByUser; } void KParts::OpenUrlArguments::setActionRequestedByUser(bool userRequested) { d->actionRequestedByUser = userRequested; } #include "moc_part.cpp"