diff --git a/src/kptfactory.cpp b/src/kptfactory.cpp index 5f32f6e1..c5fa3818 100644 --- a/src/kptfactory.cpp +++ b/src/kptfactory.cpp @@ -1,92 +1,98 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis 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. */ // clazy:excludeall=qstring-arg #include "kptfactory.h" #include "kptmaindocument.h" #include "kptpart.h" #include "kptaboutdata.h" #include #include #include #include +#include namespace KPlato { KoComponentData* Factory::s_global = 0L; KAboutData* Factory::s_aboutData = 0L; Factory::Factory() : KPluginFactory() { global(); } Factory::~Factory() { delete s_aboutData; s_aboutData = 0L; delete s_global; s_global = 0L; } QObject* Factory::create( const char* /*iface*/, QWidget* /*parentWidget*/, QObject *parent, const QVariantList& args, const QString& keyword ) { Q_UNUSED( args ); Q_UNUSED( keyword ); Part *part = new Part(parent); MainDocument *doc = new MainDocument(part); part->setDocument(doc); + // start checking for workpackages + QTimer *timer = new QTimer(doc); + connect(timer, &QTimer::timeout, doc, &MainDocument::autoCheckForWorkPackages); + timer->start(5000); + return part; } KAboutData* Factory::aboutData() { if ( !s_aboutData ) s_aboutData = newAboutData(); return s_aboutData; } const KoComponentData &Factory::global() { if ( !s_global ) { debugPlan; s_global = new KoComponentData( *aboutData() ); // Add any application-specific resource directories here KoResourcePaths::addResourceType("calligraplan_taskmodules", "data", "calligraplan/taskmodules/"); // Tell the iconloader about share/apps/calligra/icons // KIconLoader::global()->addAppDir("calligra"); // KoDockRegistry *dockRegistry = KoDockRegistry::instance(); // dockRegistry->remove("StencilBox"); //don't want this in plan } return *s_global; } } // KPlato namespace diff --git a/src/kptmaindocument.cpp b/src/kptmaindocument.cpp index ef904403..ad010785 100644 --- a/src/kptmaindocument.cpp +++ b/src/kptmaindocument.cpp @@ -1,1580 +1,1586 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2004, 2010, 2012 Dag Andersen Copyright (C) 2006 Raphael Langerhorst Copyright (C) 2007 Thorsten Zachmann 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. */ // clazy:excludeall=qstring-arg #include "kptmaindocument.h" #include "kptpart.h" #include "kptview.h" #include "kptfactory.h" #include "kptproject.h" #include "kptlocale.h" #include "kptresource.h" #include "kptcontext.h" #include "kptschedulerpluginloader.h" #include "kptschedulerplugin.h" #include "kptbuiltinschedulerplugin.h" #include "kptcommand.h" #include "calligraplansettings.h" #include "kpttask.h" #include "KPlatoXmlLoader.h" #include "XmlSaveContext.h" #include "kptpackage.h" #include "kptworkpackagemergedialog.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KHOLIDAYS #include #endif namespace KPlato { MainDocument::MainDocument(KoPart *part) : KoDocument(part), m_project( 0 ), m_context( 0 ), m_xmlLoader(), m_loadingTemplate( false ), m_loadingSharedResourcesTemplate( false ), m_viewlistModified( false ), m_checkingForWorkPackages( false ), m_loadingSharedProject(false), m_skipSharedProjects(false), m_isTaskModule(false) { Q_ASSERT(part); setAlwaysAllowSaving(true); m_config.setReadWrite( true ); loadSchedulerPlugins(); setProject( new Project( m_config ) ); // after config & plugins are loaded m_project->setId( m_project->uniqueNodeId() ); m_project->registerNodeId( m_project ); // register myself connect(this, &MainDocument::insertSharedProject, this, &MainDocument::slotInsertSharedProject); - - QTimer::singleShot ( 5000, this, &MainDocument::autoCheckForWorkPackages ); } MainDocument::~MainDocument() { qDeleteAll( m_schedulerPlugins ); if ( m_project ) { m_project->deref(); // deletes if last user } qDeleteAll( m_mergedPackages ); delete m_context; } void MainDocument::setReadWrite( bool rw ) { m_config.setReadWrite( rw ); KoDocument::setReadWrite( rw ); } void MainDocument::loadSchedulerPlugins() { // Add built-in scheduler addSchedulerPlugin( "Built-in", new BuiltinSchedulerPlugin( this ) ); // Add all real scheduler plugins SchedulerPluginLoader *loader = new SchedulerPluginLoader(this); connect(loader, &SchedulerPluginLoader::pluginLoaded, this, &MainDocument::addSchedulerPlugin); loader->loadAllPlugins(); } void MainDocument::addSchedulerPlugin( const QString &key, SchedulerPlugin *plugin) { debugPlan<setConfig( m_config ); } void MainDocument::setProject( Project *project ) { if ( m_project ) { disconnect( m_project, &Project::projectChanged, this, &MainDocument::changed ); delete m_project; } m_project = project; if ( m_project ) { connect( m_project, &Project::projectChanged, this, &MainDocument::changed ); // m_project->setConfig( config() ); m_project->setSchedulerPlugins( m_schedulerPlugins ); } m_aboutPage.setProject( project ); emit changed(); } bool MainDocument::loadOdf( KoOdfReadStore &odfStore ) { warnPlan<< "OpenDocument not supported, let's try native xml format"; return loadXML( odfStore.contentDoc(), 0 ); // We have only one format, so try to load that! } bool MainDocument::loadXML( const KoXmlDocument &document, KoStore* ) { QPointer updater; if (progressUpdater()) { updater = progressUpdater()->startSubtask(1, "Plan::Part::loadXML"); updater->setProgress(0); m_xmlLoader.setUpdater( updater ); } QString value; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlan << "No mime type specified!"; setErrorMessage( i18n( "Invalid document. No mimetype specified." ) ); return false; } if ( value == "application/x-vnd.kde.kplato" ) { if (updater) { updater->setProgress(5); } m_xmlLoader.setMimetype( value ); QString message; Project *newProject = new Project(m_config, false); KPlatoXmlLoader loader( m_xmlLoader, newProject ); bool ok = loader.load( plan ); if ( ok ) { setProject( newProject ); setModified( false ); debugPlan<schedules(); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach ( Node *n, newProject->nodeDict()) { foreach ( Schedule *s, n->schedules()) { if ( s->isDeleted() ) { // true also if parent == 0 errorPlan<name()<takeSchedule( s ); delete s; } } } } else { setErrorMessage( loader.errorMessage() ); delete newProject; } if (updater) { updater->setProgress(100); // the rest is only processing, not loading } emit changed(); return ok; } if ( value != "application/x-vnd.kde.plan" ) { errorPlan << "Unknown mime type " << value; setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan, got %1", value ) ); return false; } QString syntaxVersion = plan.attribute( "version", PLAN_FILE_SYNTAX_VERSION ); m_xmlLoader.setVersion( syntaxVersion ); if ( syntaxVersion > PLAN_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document was created with a newer version of Plan (syntax version: %1)\n" "Opening it in this version of Plan will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { setErrorMessage( "USER_CANCELED" ); return false; } } if (updater) updater->setProgress(5); /* #ifdef KOXML_USE_QDOM int numNodes = plan.childNodes().count(); #else int numNodes = plan.childNodesCount(); #endif */ #if 0 This test does not work any longer. KoXml adds a couple of elements not present in the file!! if ( numNodes > 2 ) { //TODO: Make a proper bitching about this debugPlan <<"*** Error ***"; debugPlan <<" Children count should be maximum 2, but is" << numNodes; return false; } #endif m_xmlLoader.startLoad(); KoXmlNode n = plan.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "project" ) { Project *newProject = new Project(m_config, false); m_xmlLoader.setProject( newProject ); if ( newProject->load( e, m_xmlLoader ) ) { if ( newProject->id().isEmpty() ) { newProject->setId( newProject->uniqueNodeId() ); newProject->registerNodeId( newProject ); } // The load went fine. Throw out the old project setProject( newProject ); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach ( Node *n, newProject->nodeDict()) { foreach ( Schedule *s, n->schedules()) { if ( s->isDeleted() ) { // true also if parent == 0 errorPlan<name()<takeSchedule( s ); delete s; } } } } else { delete newProject; m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of project failed" ); //TODO add some ui here } } } m_xmlLoader.stopLoad(); if (updater) updater->setProgress(100); // the rest is only processing, not loading setModified( false ); emit changed(); return true; } QDomDocument MainDocument::saveXML() { debugPlan; // Save the project XmlSaveContext context(m_project); context.save(); return context.document; } QDomDocument MainDocument::saveWorkPackageXML( const Node *node, long id, Resource *resource ) { debugPlan; QDomDocument document( "plan" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement doc = document.createElement( "planwork" ); doc.setAttribute( "editor", "Plan" ); doc.setAttribute( "mime", "application/x-vnd.kde.plan.work" ); doc.setAttribute( "version", PLANWORK_FILE_SYNTAX_VERSION ); doc.setAttribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ); document.appendChild( doc ); // Work package info QDomElement wp = document.createElement( "workpackage" ); if ( resource ) { wp.setAttribute( "owner", resource->name() ); wp.setAttribute( "owner-id", resource->id() ); } wp.setAttribute( "time-tag", QDateTime::currentDateTime().toString( Qt::ISODate ) ); doc.appendChild( wp ); // Save the project m_project->saveWorkPackageXML( doc, node, id ); return document; } bool MainDocument::saveWorkPackageToStream( QIODevice *dev, const Node *node, long id, Resource *resource ) { QDomDocument doc = saveWorkPackageXML( node, id, resource ); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open( QIODevice::WriteOnly ); int nwritten = dev->write( s.data(), s.size() ); if ( nwritten != (int)s.size() ) { warnPlan<<"wrote:"<m_specialOutputFlag == SaveEncrypted ) { backend = KoStore::Encrypted; debugPlan <<"Saving using encrypted backend."; }*/ #endif QByteArray mimeType = "application/x-vnd.kde.plan.work"; debugPlan <<"MimeType=" << mimeType; KoStore *store = KoStore::createStore( file, KoStore::Write, mimeType, backend ); /* if ( d->m_specialOutputFlag == SaveEncrypted && !d->m_password.isNull( ) ) { store->setPassword( d->m_password ); }*/ if ( store->bad() ) { setErrorMessage( i18n( "Could not create the workpackage file for saving: %1", file ) ); // more details needed? delete store; return false; } // Tell KoStore not to touch the file names if ( ! store->open( "root" ) ) { setErrorMessage( i18n( "Not able to write '%1'. Partition full?", QString( "maindoc.xml") ) ); delete store; return false; } KoStoreDevice dev( store ); if ( !saveWorkPackageToStream( &dev, node, id, resource ) || !store->close() ) { debugPlan <<"saveToStream failed"; delete store; return false; } node->documents().saveToStore( store ); debugPlan <<"Saving done of url:" << file; if ( !store->finalize() ) { delete store; return false; } // Success delete store; return true; } bool MainDocument::saveWorkPackageUrl( const QUrl &_url, const Node *node, long id, Resource *resource ) { //debugPlan<<_url; QApplication::setOverrideCursor( Qt::WaitCursor ); emit statusBarMessage( i18n("Saving...") ); bool ret = false; ret = saveWorkPackageFormat( _url.path(), node, id, resource ); // kzip don't handle file:// QApplication::restoreOverrideCursor(); emit clearStatusBarMessage(); return ret; } bool MainDocument::loadWorkPackage( Project &project, const QUrl &url ) { debugPlan<bad() ) { // d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file ); debugPlan<<"bad store"<open( "root" ) ) { // "old" file format (maindoc.xml) // i18n( "File does not have a maindoc.xml: %1", file ); debugPlan<<"No root"<device(), &errorMsg, &errorLine, &errorColumn ); if ( ! ok ) { errorPlan << "Parsing error in " << url.url() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; //d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4",filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8)); } else { package = loadWorkPackageXML( project, store->device(), doc, url ); if ( package ) { package->url = url; m_workpackages.insert( package->timeTag, package ); } else { ok = false; } } store->close(); //### if ( ok && package && package->settings.documents ) { ok = extractFiles( store, package ); } delete store; if ( ! ok ) { // QApplication::restoreOverrideCursor(); return false; } return true; } Package *MainDocument::loadWorkPackageXML( Project &project, QIODevice *, const KoXmlDocument &document, const QUrl &/*url*/ ) { QString value; bool ok = true; Project *proj = 0; Package *package = 0; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { debugPlan << "No mime type specified!"; setErrorMessage( i18n( "Invalid document. No mimetype specified." ) ); return 0; } else if ( value == "application/x-vnd.kde.kplato.work" ) { m_xmlLoader.setMimetype( value ); m_xmlLoader.setWorkVersion( plan.attribute( "version", "0.0.0" ) ); proj = new Project(); KPlatoXmlLoader loader( m_xmlLoader, proj ); ok = loader.loadWorkpackage( plan ); if ( ! ok ) { setErrorMessage( loader.errorMessage() ); delete proj; return 0; } package = loader.package(); package->timeTag = QDateTime::fromString( loader.timeTag(), Qt::ISODate ); } else if ( value != "application/x-vnd.kde.plan.work" ) { debugPlan << "Unknown mime type " << value; setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value ) ); return 0; } else { QString syntaxVersion = plan.attribute( "version", "0.0.0" ); m_xmlLoader.setWorkVersion( syntaxVersion ); if ( syntaxVersion > PLANWORK_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document was created with a newer version of PlanWork (syntax version: %1)\n" "Opening it in this version of PlanWork will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { setErrorMessage( "USER_CANCELED" ); return 0; } } m_xmlLoader.setVersion( plan.attribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ) ); m_xmlLoader.startLoad(); proj = new Project(); package = new Package(); package->project = proj; KoXmlNode n = plan.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "project" ) { m_xmlLoader.setProject( proj ); ok = proj->load( e, m_xmlLoader ); if ( ! ok ) { m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" ); //TODO add some ui here } } else if ( e.tagName() == "workpackage" ) { package->timeTag = QDateTime::fromString( e.attribute( "time-tag" ), Qt::ISODate ); package->ownerId = e.attribute( "owner-id" ); package->ownerName = e.attribute( "owner" ); debugPlan<<"workpackage:"<timeTag<ownerId<ownerName; KoXmlElement elem; forEachElement( elem, e ) { if ( elem.tagName() != "settings" ) { continue; } package->settings.usedEffort = (bool)elem.attribute( "used-effort" ).toInt(); package->settings.progress = (bool)elem.attribute( "progress" ).toInt(); package->settings.documents = (bool)elem.attribute( "documents" ).toInt(); } } } if ( proj->numChildren() > 0 ) { package->task = static_cast( proj->childNode( 0 ) ); package->toTask = qobject_cast( m_project->findNode( package->task->id() ) ); WorkPackage &wp = package->task->workPackage(); if ( wp.ownerId().isEmpty() ) { wp.setOwnerId( package->ownerId ); wp.setOwnerName( package->ownerName ); } debugPlan<<"Task set:"<task->name(); } m_xmlLoader.stopLoad(); } if ( ok && proj->id() == project.id() && proj->childNode( 0 ) ) { ok = project.nodeDict().contains( proj->childNode( 0 )->id() ); if ( ok && m_mergedPackages.contains( package->timeTag ) ) { ok = false; // already merged } if ( ok && package->timeTag.isValid() && ! m_mergedPackages.contains( package->timeTag ) ) { m_mergedPackages[ package->timeTag ] = proj; // register this for next time } if ( ok && ! package->timeTag.isValid() ) { warnPlan<<"Work package is not time tagged:"<childNode( 0 )->name()<url; ok = false; } } if ( ! ok ) { delete proj; delete package; return 0; } Q_ASSERT( package ); return package; } bool MainDocument::extractFiles( KoStore *store, Package *package ) { if ( package->task == 0 ) { errorPlan<<"No task!"; return false; } foreach ( Document *doc, package->task->documents().documents() ) { if ( ! doc->isValid() || doc->type() != Document::Type_Product || doc->sendAs() != Document::SendAs_Copy ) { continue; } if ( ! extractFile( store, package, doc ) ) { return false; } } return true; } bool MainDocument::extractFile( KoStore *store, Package *package, const Document *doc ) { QTemporaryFile tmpfile; if ( ! tmpfile.open() ) { errorPlan<<"Failed to open temporary file"; return false; } if ( ! store->extractFile( doc->url().fileName(), tmpfile.fileName() ) ) { errorPlan<<"Failed to extract file:"<url().fileName()<<"to:"<documents.insert( tmpfile.fileName(), doc->url() ); tmpfile.setAutoRemove( false ); debugPlan<<"extracted:"<url().fileName()<<"->"<(sender()); if ( m_config.checkForWorkPackages() ) { checkForWorkPackages( true ); } - QTimer::singleShot ( 10000, this, &MainDocument::autoCheckForWorkPackages ); + if (timer && timer->interval() != 10000) { + timer->stop(); + timer->setInterval(10000); + timer->start(); + } } void MainDocument::checkForWorkPackages( bool keep ) { if ( m_checkingForWorkPackages || m_config.retrieveUrl().isEmpty() || m_project == 0 || m_project->numChildren() == 0 ) { return; } - m_checkingForWorkPackages = true; if ( ! keep ) { qDeleteAll( m_mergedPackages ); m_mergedPackages.clear(); } QDir dir( m_config.retrieveUrl().path(), "*.planwork" ); m_infoList = dir.entryInfoList( QDir::Files | QDir::Readable, QDir::Time ); checkForWorkPackage(); return; } void MainDocument::checkForWorkPackage() { if ( ! m_infoList.isEmpty() ) { + m_checkingForWorkPackages = true; loadWorkPackage( *m_project, QUrl::fromLocalFile( m_infoList.takeLast().absoluteFilePath() ) ); if ( ! m_infoList.isEmpty() ) { QTimer::singleShot ( 0, this, &MainDocument::checkForWorkPackage ); return; } // all files read // remove other projects QMutableMapIterator it( m_workpackages ); while ( it.hasNext() ) { it.next(); Package *package = it.value(); if ( package->project->id() != m_project->id() ) { delete package->project; delete package; it.remove(); } } // Merge our workpackages if ( ! m_workpackages.isEmpty() ) { WorkPackageMergeDialog *dlg = new WorkPackageMergeDialog( i18n( "New work packages detected. Merge data with existing tasks?" ), m_workpackages ); connect(dlg, &QDialog::finished, this, &MainDocument::workPackageMergeDialogFinished); dlg->show(); dlg->raise(); dlg->activateWindow(); + } else { + m_checkingForWorkPackages = false; } } } void MainDocument::workPackageMergeDialogFinished( int result ) { WorkPackageMergeDialog *dlg = qobject_cast( sender() ); + Q_ASSERT(dlg); if ( dlg == 0 ) { return; } if ( result == KoDialog::Yes ) { // merge the oldest first foreach( int i, dlg->checkedList() ) { const QList &packages = m_workpackages.values(); mergeWorkPackage(packages.at(i)); } // 'Yes' was hit so terminate all packages foreach(const Package *p, m_workpackages) { terminateWorkPackage( p ); } } qDeleteAll( m_workpackages ); m_workpackages.clear(); m_checkingForWorkPackages = false; dlg->deleteLater(); } void MainDocument::mergeWorkPackages() { foreach (const Package *package, m_workpackages) { mergeWorkPackage( package ); } } void MainDocument::terminateWorkPackage( const Package *package ) { QFile file( package->url.path() ); if ( ! file.exists() ) { return; } if ( KPlatoSettings::deleteFile() || KPlatoSettings::saveUrl().isEmpty() ) { file.remove(); } else if ( KPlatoSettings::saveFile() && ! KPlatoSettings::saveUrl().isEmpty() ) { QDir dir( KPlatoSettings::saveUrl().path() ); if ( ! dir.exists() ) { if ( ! dir.mkpath( dir.path() ) ) { //TODO message debugPlan<<"Could not create directory:"<project); if ( proj.id() == m_project->id() && proj.childNode( 0 ) ) { const Task *from = package->task; Task *to = package->toTask; if ( to && from ) { mergeWorkPackage( to, from, package ); } } } void MainDocument::mergeWorkPackage( Task *to, const Task *from, const Package *package ) { Resource *resource = m_project->findResource( package->ownerId ); if ( resource == 0 ) { KMessageBox::error( 0, i18n( "The package owner '%1' is not a resource in this project. You must handle this manually.", package->ownerName ) ); return; } MacroCommand *cmd = new MacroCommand( kundo2_noi18n("Merge workpackage") ); Completion &org = to->completion(); const Completion &curr = from->completion(); if ( package->settings.progress ) { if ( org.isStarted() != curr.isStarted() ) { cmd->addCommand( new ModifyCompletionStartedCmd(org, curr.isStarted() ) ); } if ( org.isFinished() != curr.isFinished() ) { cmd->addCommand( new ModifyCompletionFinishedCmd( org, curr.isFinished() ) ); } if ( org.startTime() != curr.startTime() ) { cmd->addCommand( new ModifyCompletionStartTimeCmd( org, curr.startTime() ) ); } if ( org.finishTime() != curr.finishTime() ) { cmd->addCommand( new ModifyCompletionFinishTimeCmd( org, curr.finishTime() ) ); } // TODO: review how/if to merge data from different resources // remove entries const QList &dates = org.entries().keys(); for (const QDate &d : dates) { if ( ! curr.entries().contains( d ) ) { debugPlan<<"remove entry "<addCommand( new RemoveCompletionEntryCmd( org, d ) ); } } // add new entries / modify existing const QList &cdates = curr.entries().keys(); for (const QDate &d : cdates) { if ( org.entries().contains( d ) && curr.entry( d ) == org.entry( d ) ) { continue; } Completion::Entry *e = new Completion::Entry( *( curr.entry( d ) ) ); cmd->addCommand( new ModifyCompletionEntryCmd( org, d, e ) ); } } if ( package->settings.usedEffort ) { Completion::UsedEffort *ue = new Completion::UsedEffort(); Completion::Entry prev; Completion::EntryList::ConstIterator entriesIt = curr.entries().constBegin(); const Completion::EntryList::ConstIterator entriesEnd = curr.entries().constEnd(); for (; entriesIt != entriesEnd; ++entriesIt) { const QDate &d = entriesIt.key(); const Completion::Entry &e = *entriesIt.value(); // set used effort from date entry and remove used effort from date entry Completion::UsedEffort::ActualEffort effort( e.totalPerformed - prev.totalPerformed ); ue->setEffort( d, effort ); prev = e; } cmd->addCommand( new AddCompletionUsedEffortCmd( org, resource, ue ) ); } bool docsaved = false; if ( package->settings.documents ) { //TODO: handle remote files QMap::const_iterator it = package->documents.constBegin(); QMap::const_iterator end = package->documents.constEnd(); for ( ; it != end; ++it ) { const QUrl src = QUrl::fromLocalFile(it.key()); KIO::CopyJob *job = KIO::move( src, it.value(), KIO::Overwrite ); if ( job->exec() ) { docsaved = true; //TODO: async debugPlan<<"Moved file:"<isEmpty() ) { KMessageBox::information( 0, i18n( "Nothing to save from this package" ) ); } // add a copy to our tasks list of transmitted packages WorkPackage *wp = new WorkPackage( from->workPackage() ); wp->setParentTask( to ); if ( ! wp->transmitionTime().isValid() ) { wp->setTransmitionTime( package->timeTag ); } wp->setTransmitionStatus( WorkPackage::TS_Receive ); cmd->addCommand( new WorkPackageAddCmd( m_project, to, wp ) ); addCommand( cmd ); } void MainDocument::paintContent( QPainter &, const QRect &) { // Don't embed this app!!! } void MainDocument::slotViewDestroyed() { } void MainDocument::setLoadingTemplate(bool loading) { m_loadingTemplate = loading; } void MainDocument::setLoadingSharedResourcesTemplate(bool loading) { m_loadingSharedResourcesTemplate = loading; } bool MainDocument::completeLoading( KoStore *store ) { // If we get here the new project is loaded and set if (m_loadingSharedProject) { // this file is loaded by another project // to read resource appointments, // so we must not load any extra stuff return true; } if ( m_loadingTemplate ) { //debugPlan<<"Loading template, generate unique ids"; m_project->generateUniqueIds(); m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) ); m_project->setConstraintEndTime( m_project->constraintStartTime().addYears( 2 ) ); m_project->locale()->setCurrencyLocale(QLocale::AnyLanguage, QLocale::AnyCountry); m_project->locale()->setCurrencySymbol(QString()); } else if ( isImporting() ) { // NOTE: I don't think this is a good idea. // Let the filter generate ids for non-plan files. // If the user wants to create a new project from an old one, // he should use Tools -> Insert Project File //m_project->generateUniqueNodeIds(); } if (m_loadingSharedResourcesTemplate && m_project->calendarCount() > 0) { Calendar *c = m_project->calendarAt(0); c->setTimeZone(QTimeZone::systemTimeZone()); } if (m_project->useSharedResources() && !m_project->sharedResourcesFile().isEmpty() && !m_skipSharedProjects) { QUrl url = QUrl::fromLocalFile(m_project->sharedResourcesFile()); if (url.isValid()) { insertResourcesFile(url, m_project->loadProjectsAtStartup() ? m_project->sharedProjectsUrl() : QUrl()); } } if ( store == 0 ) { // can happen if loading a template debugPlan<<"No store"; return true; // continue anyway } delete m_context; m_context = new Context(); KoXmlDocument doc; if ( loadAndParse( store, "context.xml", doc ) ) { store->close(); m_context->load( doc ); } else warnPlan<<"No context"; return true; } // TODO: // Due to splitting of KoDocument into a document and a part, // we simulate the old behaviour by registering all views in the document. // Find a better solution! void MainDocument::registerView( View* view ) { if ( view && ! m_views.contains( view ) ) { m_views << QPointer( view ); } } bool MainDocument::completeSaving( KoStore *store ) { foreach ( View *view, m_views ) { if ( view ) { if ( store->open( "context.xml" ) ) { if ( m_context == 0 ) m_context = new Context(); QDomDocument doc = m_context->save( view ); KoStoreDevice dev( store ); QByteArray s = doc.toByteArray(); // this is already Utf8! (void)dev.write( s.data(), s.size() ); (void)store->close(); m_viewlistModified = false; emit viewlistModified( false ); } break; } } return true; } bool MainDocument::loadAndParse(KoStore *store, const QString &filename, KoXmlDocument &doc) { //debugPlan << "oldLoadAndParse: Trying to open " << filename; if (!store->open(filename)) { warnPlan << "Entry " << filename << " not found!"; // d->lastErrorMessage = i18n( "Could not find %1",filename ); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn ); if ( !ok ) { errorPlan << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; /* d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4" ,filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8));*/ store->close(); return false; } debugPlan << "File " << filename << " loaded and parsed"; return true; } void MainDocument::insertFile( const QUrl &url, Node *parent, Node *after ) { Part *part = new Part( this ); MainDocument *doc = new MainDocument( part ); part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->m_insertFileInfo.url = url; doc->m_insertFileInfo.parent = parent; doc->m_insertFileInfo.after = after; connect(doc, &KoDocument::completed, this, &MainDocument::insertFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); doc->openUrl( url ); } void MainDocument::insertFileCompleted() { debugPlan<( sender() ); if ( doc ) { Project &p = doc->getProject(); insertProject( p, doc->m_insertFileInfo.parent, doc->m_insertFileInfo.after ); doc->documentPart()->deleteLater(); // also deletes document } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertResourcesFile(const QUrl &url, const QUrl &projects) { insertSharedProjects(projects); // prepare for insertion after shared resources m_sharedProjectsFiles.removeAll(url); // resource file is not a project Part *part = new Part( this ); MainDocument *doc = new MainDocument( part ); doc->m_skipSharedProjects = true; // should not have shared projects, but... part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->setCheckAutoSaveFile(false); connect(doc, &KoDocument::completed, this, &MainDocument::insertResourcesFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); doc->openUrl( url ); } void MainDocument::insertResourcesFileCompleted() { debugPlanShared<( sender() ); if (doc) { Project &p = doc->getProject(); mergeResources(p); m_project->setSharedResourcesLoaded(true); doc->documentPart()->deleteLater(); // also deletes document slotInsertSharedProject(); // insert shared bookings } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertFileCancelled( const QString &error ) { debugPlan<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } void MainDocument::clearResourceAssignments() { foreach (Resource *r, m_project->resourceList()) { r->clearExternalAppointments(); } } void MainDocument::loadResourceAssignments(QUrl url) { insertSharedProjects(url); slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QList &urls) { clearResourceAssignments(); m_sharedProjectsFiles = urls; slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QUrl &url) { m_sharedProjectsFiles.clear(); QFileInfo fi(url.path()); if (!fi.exists()) { return; } if (fi.isFile()) { m_sharedProjectsFiles = QList() << url; debugPlan<<"Get all projects in file:"<m_skipSharedProjects = true; // never load recursively part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->setCheckAutoSaveFile(false); doc->m_loadingSharedProject = true; connect(doc, &KoDocument::completed, this, &MainDocument::insertSharedProjectCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertSharedProjectCancelled); doc->openUrl(m_sharedProjectsFiles.takeFirst()); } void MainDocument::insertSharedProjectCompleted() { debugPlanShared<( sender() ); if (doc) { Project &p = doc->getProject(); debugPlanShared<id()<<"Loaded project:"<id() && p.isScheduled(ANYSCHEDULED)) { // FIXME: improve! // find a suitable schedule ScheduleManager *sm = 0; foreach(ScheduleManager *m, p.allScheduleManagers()) { if (m->isBaselined()) { sm = m; break; } if (m->isScheduled()) { sm = m; // take the last one, more likely to be subschedule } } if (sm) { foreach(Resource *r, p.resourceList()) { Resource *res = m_project->resource(r->id()); if (res && res->isShared()) { Appointment *app = new Appointment(); app->setAuxcilliaryInfo(p.name()); foreach(const Appointment *a, r->appointments(sm->scheduleId())) { *app += *a; } if (app->isEmpty()) { delete app; } else { res->addExternalAppointment(p.id(), app); debugPlanShared<name()<<"added:"<auxcilliaryInfo()<documentPart()->deleteLater(); // also deletes document emit insertSharedProject(); // do next file } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertSharedProjectCancelled( const QString &error ) { debugPlanShared<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } bool MainDocument::insertProject( Project &project, Node *parent, Node *after ) { debugPlan<<&project; // make sure node ids in new project is unique also in old project QList existingIds = m_project->nodeDict().keys(); foreach ( Node *n, project.allNodes() ) { QString oldid = n->id(); n->setId( project.uniqueNodeId( existingIds ) ); project.removeId( oldid ); // remove old id project.registerNodeId( n ); // register new id } MacroCommand *m = new InsertProjectCmd( project, parent==0?m_project:parent, after, kundo2_i18n( "Insert project" ) ); if ( m->isEmpty() ) { delete m; } else { addCommand( m ); } return true; } // check if calendar 'c' has children that will not be removed (normally 'Local' calendars) bool canRemoveCalendar(const Calendar *c, const QList &lst) { for (Calendar *cc : c->calendars()) { if (!lst.contains(cc)) { return false; } if (!canRemoveCalendar(cc, lst)) { return false; } } return true; } // sort parent calendars before children QList sortedRemoveCalendars(Project &shared, const QList &lst) { QList result; for (Calendar *c : lst) { if (c->isShared() && !shared.calendar(c->id())) { result << c; } result += sortedRemoveCalendars(shared, c->calendars()); } return result; } bool MainDocument::mergeResources(Project &project) { debugPlanShared<<&project; // Just in case, remove stuff not related to resources foreach(Node *n, project.childNodeIterator()) { debugPlanShared<<"Project not empty, delete node:"<name(); NodeDeleteCmd cmd(n); cmd.execute(); } foreach(ScheduleManager *m, project.scheduleManagers()) { debugPlanShared<<"Project not empty, delete schedule:"<name(); DeleteScheduleManagerCmd cmd(project, m); cmd.execute(); } foreach(Account *a, project.accounts().accountList()) { debugPlanShared<<"Project not empty, delete account:"<name(); RemoveAccountCmd cmd(project, a); cmd.execute(); } // Mark all resources / groups as shared foreach(ResourceGroup *g, project.resourceGroups()) { g->setShared(true); } foreach(Resource *r, project.resourceList()) { r->setShared(true); } // Mark all calendars shared foreach(Calendar *c, project.allCalendars()) { c->setShared(true); } // check if any shared stuff has been removed QList removedGroups; QList removedResources; QList removedCalendars; QStringList removed; foreach(ResourceGroup *g, m_project->resourceGroups()) { if (g->isShared() && !project.findResourceGroup(g->id())) { removedGroups << g; removed << i18n("Group: %1", g->name()); } } foreach(Resource *r, m_project->resourceList()) { if (r->isShared() && !project.findResource(r->id())) { removedResources << r; removed << i18n("Resource: %1", r->name()); } } removedCalendars = sortedRemoveCalendars(project, m_project->calendars()); for (Calendar *c : qAsConst(removedCalendars)) { removed << i18n("Calendar: %1", c->name()); } if (!removed.isEmpty()) { KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancelList( 0, i18n("Shared resources has been removed from the shared resources file." "\nSelect how they shall be treated in this project."), removed, xi18nc("@title:window", "Shared resources"), KStandardGuiItem::remove(), KGuiItem(i18n("Convert")), KGuiItem(i18n("Keep")) ); switch (result) { case KMessageBox::Yes: // Remove for (Resource *r : qAsConst(removedResources)) { RemoveResourceCmd cmd(r->parentGroup(), r); cmd.redo(); } for (ResourceGroup *g : qAsConst(removedGroups)) { if (g->resources().isEmpty()) { RemoveResourceGroupCmd cmd(m_project, g); cmd.redo(); } else { // we may have put local resource(s) in this group // so we need to keep it g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } } for (Calendar *c : qAsConst(removedCalendars)) { CalendarRemoveCmd cmd(m_project, c); cmd.redo(); } break; case KMessageBox::No: // Convert for (Resource *r : qAsConst(removedResources)) { r->setShared(false); m_project->removeResourceId(r->id()); r->setId(m_project->uniqueResourceId()); m_project->insertResourceId(r->id(), r); } for (ResourceGroup *g : qAsConst(removedGroups)) { g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } for (Calendar *c : qAsConst(removedCalendars)) { c->setShared(false); m_project->removeCalendarId(c->id()); c->setId(m_project->uniqueCalendarId()); m_project->insertCalendarId(c->id(), c); } break; case KMessageBox::Cancel: // Keep break; default: break; } } // update values of already existing objects QStringList l1; foreach(ResourceGroup *g, project.resourceGroups()) { l1 << g->id(); } QStringList l2; foreach(ResourceGroup *g, m_project->resourceGroups()) { l2 << g->id(); } debugPlanShared< removegroups; foreach(ResourceGroup *g, project.resourceGroups()) { ResourceGroup *group = m_project->findResourceGroup(g->id()); if (group) { if (!group->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the group from shared. removegroups << g; group->setShared(true); debugPlanShared<<"Set group to shared:"<id(); } group->setName(g->name()); group->setType(g->type()); debugPlanShared<<"Updated group:"<id(); } } QList removeresources; foreach(Resource *r, project.resourceList()) { Resource *resource = m_project->findResource(r->id()); if (resource) { if (!resource->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the resource from shared. removeresources << r; resource->setShared(true); debugPlanShared<<"Set resource to shared:"<id(); } resource->setName(r->name()); resource->setInitials(r->initials()); resource->setEmail(r->email()); resource->setType(r->type()); resource->setAutoAllocate(r->autoAllocate()); resource->setAvailableFrom(r->availableFrom()); resource->setAvailableUntil(r->availableUntil()); resource->setUnits(r->units()); resource->setNormalRate(r->normalRate()); resource->setOvertimeRate(r->overtimeRate()); QString id = r->calendar(true) ? r->calendar(true)->id() : QString(); resource->setCalendar(m_project->findCalendar(id)); id = r->account() ? r->account()->name() : QString(); resource->setAccount(m_project->accounts().findAccount(id)); resource->setRequiredIds(r->requiredIds()); resource->setTeamMemberIds(r->teamMemberIds()); debugPlanShared<<"Updated resource:"<id(); } } QList removecalendars; foreach(Calendar *c, project.allCalendars()) { Calendar *calendar = m_project->findCalendar(c->id()); if (calendar) { if (!calendar->isShared()) { // User has probably created shared resources from this project, // so the calendar exists but are local ones. // Convert to shared and do not load the resource from shared. removecalendars << c; calendar->setShared(true); debugPlanShared<<"Set calendar to shared:"<id(); } *calendar = *c; debugPlanShared<<"Updated calendar:"<id(); } } debugPlanShared<<"Remove:"<childCount() == 0) { removecalendars.removeAt(i); debugPlanShared<<"Delete calendar:"<id(); CalendarRemoveCmd cmd(&project, c); cmd.execute(); } } } for (Resource *r : qAsConst(removeresources)) { debugPlanShared<<"Delete resource:"<id(); RemoveResourceCmd cmd(r->parentGroup(), r); cmd.execute(); } for (ResourceGroup *g : qAsConst(removegroups)) { debugPlanShared<<"Delete group:"<id(); RemoveResourceGroupCmd cmd(&project, g); cmd.execute(); } // insert new objects Q_ASSERT(project.childNodeIterator().isEmpty()); InsertProjectCmd cmd(project, m_project, 0); cmd.execute(); return true; } void MainDocument::insertViewListItem( View */*view*/, const ViewListItem *item, const ViewListItem *parent, int index ) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemAdded(item, parent, index); setModified( true ); m_viewlistModified = true; } void MainDocument::removeViewListItem( View */*view*/, const ViewListItem *item ) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemRemoved(item); setModified( true ); m_viewlistModified = true; } void MainDocument::setModified( bool mod ) { debugPlan<name().isEmpty()) { setUrl(QUrl(m_project->name() + ".plan")); } Calendar *week = 0; if (KPlatoSettings::generateWeek()) { bool always = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::Always; bool ifnone = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::NoneExists; if (always || (ifnone && m_project->calendarCount() == 0)) { // create a calendar week = new Calendar(i18nc("Base calendar name", "Base")); m_project->addCalendar(week); CalendarDay vd(CalendarDay::NonWorking); for (int i = Qt::Monday; i <= Qt::Sunday; ++i) { if (m_config.isWorkingday(i)) { CalendarDay wd(CalendarDay::Working); TimeInterval ti(m_config.dayStartTime(i), m_config.dayLength(i)); wd.addInterval(ti); week->setWeekday(i, wd); } else { week->setWeekday(i, vd); } } } } #ifdef HAVE_KHOLIDAYS if (KPlatoSettings::generateHolidays()) { bool inweek = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::InWeekCalendar; bool subcalendar = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSubCalendar; bool separate = week == 0 || KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSeparateCalendar; Calendar *c = 0; if (inweek) { c = week; qDebug()<addCalendar(c, week); qDebug()<addCalendar(c); qDebug()<setHolidayRegion(KPlatoSettings::region()); } #endif } // creates a "new" project from current project (new ids etc) void MainDocument::createNewProject() { setEmpty(); clearUndoHistory(); setModified( false ); resetURL(); KoDocumentInfo *info = documentInfo(); info->resetMetaData(); info->setProperty( "title", "" ); setTitleModified(); m_project->generateUniqueNodeIds(); Duration dur = m_project->constraintEndTime() - m_project->constraintStartTime(); m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) ); m_project->setConstraintEndTime( m_project->constraintStartTime() + dur ); while ( m_project->numScheduleManagers() > 0 ) { foreach ( ScheduleManager *sm, m_project->allScheduleManagers() ) { if ( sm->childCount() > 0 ) { continue; } if ( sm->expected() ) { sm->expected()->setDeleted( true ); sm->setExpected( 0 ); } m_project->takeScheduleManager( sm ); delete sm; } } foreach ( Schedule *s, m_project->schedules() ) { m_project->takeSchedule( s ); delete s; } foreach ( Node *n, m_project->allNodes() ) { foreach ( Schedule *s, n->schedules() ) { n->takeSchedule( s ); delete s; } } foreach ( Resource *r, m_project->resourceList() ) { foreach ( Schedule *s, r->schedules() ) { r->takeSchedule( s ); delete s; } } } void MainDocument::setIsTaskModule(bool value) { m_isTaskModule = value; } bool MainDocument::isTaskModule() const { return m_isTaskModule; } } //KPlato namespace diff --git a/src/kptmaindocument.h b/src/kptmaindocument.h index 627495ae..72cc3ad1 100644 --- a/src/kptmaindocument.h +++ b/src/kptmaindocument.h @@ -1,253 +1,254 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2004 - 2010 Dag Andersen Copyright (C) 2006 Raphael Langerhorst Copyright (C) 2007 Thorsten Zachmann 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. */ #ifndef KPTMAINDOCUMENT_H #define KPTMAINDOCUMENT_H #include "plan_export.h" #include "kpttask.h" #include "kptconfig.h" #include "kptwbsdefinition.h" #include "kptxmlloaderobject.h" #include "about/aboutpage.h" #include "KoDocument.h" #include #include #define PLAN_MIME_TYPE "application/x-vnd.kde.plan" /// The main namespace. namespace KPlato { class DocumentChild; class Project; class Context; class SchedulerPlugin; class ViewListItem; class View; class Package; class PLAN_EXPORT MainDocument : public KoDocument { Q_OBJECT public: explicit MainDocument(KoPart *part); ~MainDocument(); /// reimplemented from KoDocument virtual QByteArray nativeFormatMimeType() const { return PLAN_MIME_TYPE; } /// reimplemented from KoDocument virtual QByteArray nativeOasisMimeType() const { return ""; } /// reimplemented from KoDocument virtual QStringList extraNativeMimeTypes() const { return QStringList() << PLAN_MIME_TYPE; } void setReadWrite( bool rw ); void configChanged(); virtual void paintContent( QPainter& painter, const QRect& rect); void setProject( Project *project ); Project &getProject() { return *m_project; } const Project &getProject() const { return * m_project; } /** * Return the set of SupportedSpecialFormats that the kplato wants to * offer in the "Save" file dialog. * Note: SaveEncrypted is not supported. */ virtual int supportedSpecialFormats() const { return SaveAsDirectoryStore; } // The load and save functions. Look in the file kplato.dtd for info virtual bool loadXML( const KoXmlDocument &document, KoStore *store ); virtual QDomDocument saveXML(); /// Save a workpackage file containing @p node with schedule identity @p id, owned by @p resource QDomDocument saveWorkPackageXML( const Node *node, long id, Resource *resource = 0 ); bool saveOdf( SavingContext &/*documentContext */) { return false; } bool loadOdf( KoOdfReadStore & odfStore ); Config &config() { return m_config; } Context *context() const { return m_context; } WBSDefinition &wbsDefinition() { return m_project->wbsDefinition(); } const XMLLoaderObject &xmlLoader() const { return m_xmlLoader; } DocumentChild *createChild( KoDocument *doc, const QRect &geometry = QRect() ); bool saveWorkPackageToStream( QIODevice * dev, const Node *node, long id, Resource *resource = 0 ); bool saveWorkPackageFormat( const QString &file, const Node *node, long id, Resource *resource = 0 ); bool saveWorkPackageUrl( const QUrl & _url, const Node *node, long id, Resource *resource = 0 ); void mergeWorkPackages(); void mergeWorkPackage( const Package *package ); void terminateWorkPackage( const Package *package ); /// Load the workpackage from @p url into @p project. Return true if successful, else false. bool loadWorkPackage( Project &project, const QUrl &url ); Package *loadWorkPackageXML( Project& project, QIODevice*, const KoXmlDocument& document, const QUrl& url ); QMap workPackages() const { return m_workpackages; } void insertFile( const QUrl &url, Node *parent, Node *after = 0 ); bool insertProject( Project &project, Node *parent, Node *after ); bool mergeResources(Project &project); KPlatoAboutPage &aboutPage() { return m_aboutPage; } bool extractFiles( KoStore *store, Package *package ); bool extractFile( KoStore *store, Package *package, const Document *doc ); void registerView( View *view ); /// Create a new project from this project /// Generates new project id and task ids /// Keeps resource- and calendar ids void createNewProject(); bool isTaskModule() const; using KoDocument::setModified; public Q_SLOTS: void setModified( bool mod ); /// Inserts an item into all other views than @p view void insertViewListItem(KPlato::View *view, const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); /// Removes the view list item from all other views than @p view void removeViewListItem(KPlato::View *view, const KPlato::ViewListItem *item); /// View selector has been modified void slotViewlistModified(); /// Check for workpackages /// If @p keep is true, packages that has been refused will not be checked for again void checkForWorkPackages(bool keep); void setLoadingTemplate( bool ); void setLoadingSharedResourcesTemplate( bool ); void insertResourcesFile(const QUrl &url, const QUrl &projects = QUrl()); void slotProjectCreated(); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p urls void insertSharedProjects(const QList &urls); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p url void insertSharedProjects(const QUrl &url); /// Clear resource assignments of shared resources void clearResourceAssignments(); /// Load resource assignments of shared resources from the project(s) in @p url void loadResourceAssignments(QUrl url); void setIsTaskModule(bool value); + void autoCheckForWorkPackages(); + Q_SIGNALS: void changed(); void workPackageLoaded(); void viewlistModified( bool ); void viewListItemAdded(const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); void viewListItemRemoved(const KPlato::ViewListItem *item); void insertSharedProject(); protected: /// Load kplato specific files virtual bool completeLoading( KoStore* store ); /// Save kplato specific files virtual bool completeSaving( KoStore* store ); void mergeWorkPackage( Task *to, const Task *from, const Package *package ); // used by insert file struct InsertFileInfo { QUrl url; Node *parent; Node *after; } m_insertFileInfo; protected Q_SLOTS: void slotViewDestroyed(); void addSchedulerPlugin(const QString&, KPlato::SchedulerPlugin *plugin); - void autoCheckForWorkPackages(); void checkForWorkPackage(); void insertFileCompleted(); void insertResourcesFileCompleted(); void insertFileCancelled( const QString& ); void slotInsertSharedProject(); void insertSharedProjectCompleted(); void insertSharedProjectCancelled( const QString& ); void workPackageMergeDialogFinished( int result ); private: bool loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc); void loadSchedulerPlugins(); private: Project *m_project; QWidget* m_parentWidget; Config m_config; Context *m_context; XMLLoaderObject m_xmlLoader; bool m_loadingTemplate; bool m_loadingSharedResourcesTemplate; QMap m_schedulerPlugins; QMap m_workpackages; QFileInfoList m_infoList; QMap m_mergedPackages; KPlatoAboutPage m_aboutPage; QDomDocument m_reports; bool m_viewlistModified; bool m_checkingForWorkPackages; QList > m_views; bool m_loadingSharedProject; QList m_sharedProjectsFiles; bool m_skipSharedProjects; bool m_isTaskModule; }; } //KPlato namespace #endif diff --git a/src/libs/main/KoMainWindow.cpp b/src/libs/main/KoMainWindow.cpp index 48508a69..2c2a01ce 100644 --- a/src/libs/main/KoMainWindow.cpp +++ b/src/libs/main/KoMainWindow.cpp @@ -1,2151 +1,2151 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port 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. */ // clazy:excludeall=qstring-arg #include "KoMainWindow.h" #include "KoView.h" #include "KoDocument.h" #include "KoFilterManager.h" #include "KoDocumentInfo.h" #include "KoDocumentInfoDlg.h" #include "KoFileDialog.h" #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoPrintJob.h" #include "KoDocumentEntry.h" #include "KoPart.h" #include #include #include "KoApplication.h" #include #include "KoResourcePaths.h" #include "KoComponentData.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KACTIVITIES #include #endif // // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MainDebug.h" class KoMainWindowPrivate { public: KoMainWindowPrivate(const QByteArray &_nativeMimeType, const KoComponentData &componentData_, KoMainWindow *w) : componentData(componentData_) { nativeMimeType = _nativeMimeType; parent = w; rootDocument = 0; rootPart = 0; partToOpen = 0; mainWindowGuiIsBuilt = false; forQuit = false; activePart = 0; activeView = 0; firstTime = true; progress = 0; showDocumentInfo = 0; saveAction = 0; saveActionAs = 0; printAction = 0; printActionPreview = 0; sendFileAction = 0; exportPdf = 0; closeFile = 0; reloadFile = 0; importFile = 0; exportFile = 0; #if 0 encryptDocument = 0; #ifndef NDEBUG uncompressToDir = 0; #endif #endif isImporting = false; isExporting = false; windowSizeDirty = false; lastExportSpecialOutputFlag = 0; readOnly = false; dockWidgetMenu = 0; deferredClosingEvent = 0; #ifdef HAVE_KACTIVITIES activityResource = 0; #endif m_helpMenu = 0; // PartManger m_activeWidget = 0; m_activePart = 0; noCleanup = false; openingDocument = false; } ~KoMainWindowPrivate() { qDeleteAll(toolbarList); } void applyDefaultSettings(QPrinter &printer) { QString title = rootDocument->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { title = rootDocument->url().fileName(); // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) QMimeType mime = QMimeDatabase().mimeTypeForName(rootDocument->outputMimeType()); if (mime.isValid()) { const QString extension = mime.preferredSuffix(); if (title.endsWith(extension)) title.chop(extension.length()); } } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", parent->componentData().componentDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } QByteArray nativeMimeType; KoMainWindow *parent; KoDocument *rootDocument; QList rootViews; // PartManager QPointer rootPart; QPointer partToOpen; QPointer activePart; QPointer m_activePart; QPointer m_registeredPart; KoView *activeView; QWidget *m_activeWidget; QPointer progress; QMutex progressMutex; QList toolbarList; bool mainWindowGuiIsBuilt; bool forQuit; bool firstTime; bool windowSizeDirty; bool readOnly; QAction *showDocumentInfo; QAction *saveAction; QAction *saveActionAs; QAction *printAction; QAction *printActionPreview; QAction *sendFileAction; QAction *exportPdf; QAction *closeFile; QAction *reloadFile; QAction *importFile; QAction *exportFile; #if 0 QAction *encryptDocument; #ifndef NDEBUG QAction *uncompressToDir; #endif #endif KToggleAction *toggleDockers; KToggleAction *toggleDockerTitleBars; KRecentFilesAction *recent; bool isImporting; bool isExporting; QUrl lastExportUrl; QByteArray lastExportedFormat; int lastExportSpecialOutputFlag; QMap dockWidgetsMap; KActionMenu *dockWidgetMenu; QMap dockWidgetVisibilityMap; QList dockWidgets; QByteArray m_dockerStateBeforeHiding; QCloseEvent *deferredClosingEvent; #ifdef HAVE_KACTIVITIES KActivities::ResourceInstance *activityResource; #endif KoComponentData componentData; KHelpMenu *m_helpMenu; bool noCleanup; bool openingDocument; }; KoMainWindow::KoMainWindow(const QByteArray &nativeMimeType, const KoComponentData &componentData) : KXmlGuiWindow() , d(new KoMainWindowPrivate(nativeMimeType, componentData, this)) { #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac(true); #endif setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); connect(this, &KoMainWindow::restoringDone, this, &KoMainWindow::forceDockTabFonts); // PartManager // End QString doc; const QStringList allFiles = KoResourcePaths::findAllResources("data", "calligraplan/calligraplan_shell.rc"); setXMLFile(findMostRecentXMLFile(allFiles, doc)); setLocalXMLFile(KoResourcePaths::locateLocal("data", "calligraplan/calligraplan_shell.rc")); actionCollection()->addAction(KStandardAction::New, "file_new", this, SLOT(slotFileNew())); actionCollection()->addAction(KStandardAction::Open, "file_open", this, SLOT(slotFileOpen())); d->recent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recent, &KRecentFilesAction::recentListCleared, this, &KoMainWindow::saveRecentFiles); d->saveAction = actionCollection()->addAction(KStandardAction::Save, "file_save", this, SLOT(slotFileSave())); d->saveActionAs = actionCollection()->addAction(KStandardAction::SaveAs, "file_save_as", this, SLOT(slotFileSaveAs())); d->printAction = actionCollection()->addAction(KStandardAction::Print, "file_print", this, SLOT(slotFilePrint())); d->printActionPreview = actionCollection()->addAction(KStandardAction::PrintPreview, "file_print_preview", this, SLOT(slotFilePrintPreview())); d->exportPdf = new QAction(i18n("Print to PDF..."), this); d->exportPdf->setIcon(koIcon("application-pdf")); actionCollection()->addAction("file_export_pdf", d->exportPdf); connect(d->exportPdf, &QAction::triggered, this, static_cast(&KoMainWindow::exportToPdf)); d->sendFileAction = actionCollection()->addAction(KStandardAction::Mail, "file_send_file", this, SLOT(slotEmailFile())); d->closeFile = actionCollection()->addAction(KStandardAction::Close, "file_close", this, SLOT(slotFileClose())); actionCollection()->addAction(KStandardAction::Quit, "file_quit", this, SLOT(slotFileQuit())); d->reloadFile = new QAction(i18n("Reload"), this); actionCollection()->addAction("file_reload_file", d->reloadFile); connect(d->reloadFile, &QAction::triggered, this, &KoMainWindow::slotReloadFile); d->importFile = new QAction(koIcon("document-import"), i18n("Import..."), this); actionCollection()->addAction("file_import_file", d->importFile); connect(d->importFile, &QAction::triggered, this, &KoMainWindow::slotImportFile); d->exportFile = new QAction(koIcon("document-export"), i18n("E&xport..."), this); actionCollection()->addAction("file_export_file", d->exportFile); connect(d->exportFile, &QAction::triggered, this, &KoMainWindow::slotExportFile); #if 0 // encryption not supported d->encryptDocument = new QAction(i18n("En&crypt Document"), this); actionCollection()->addAction("file_encrypt_doc", d->encryptDocument); connect(d->encryptDocument, SIGNAL(triggered(bool)), this, SLOT(slotEncryptDocument())); #ifndef NDEBUG d->uncompressToDir = new QAction(i18n("&Uncompress to Directory"), this); actionCollection()->addAction("file_uncompress_doc", d->uncompressToDir); connect(d->uncompressToDir, SIGNAL(triggered(bool)), this, SLOT(slotUncompressToDir())); #endif #endif QAction *actionNewView = new QAction(koIcon("window-new"), i18n("&New View"), this); actionCollection()->addAction("view_newview", actionNewView); connect(actionNewView, &QAction::triggered, this, &KoMainWindow::newView); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = new QAction(koIcon("document-properties"), i18n("Document Information"), this); actionCollection()->addAction("file_documentinfo", d->showDocumentInfo); connect(d->showDocumentInfo, &QAction::triggered, this, &KoMainWindow::slotDocumentInfo); KStandardAction::keyBindings(this, SLOT(slotConfigureKeys()), actionCollection()); KStandardAction::configureToolbars(this, SLOT(slotConfigureToolbars()), actionCollection()); d->showDocumentInfo->setEnabled(false); d->saveActionAs->setEnabled(false); d->reloadFile->setEnabled(false); d->importFile->setEnabled(true); // always enabled like File --> Open d->exportFile->setEnabled(false); d->saveAction->setEnabled(false); d->printAction->setEnabled(false); d->printActionPreview->setEnabled(false); d->sendFileAction->setEnabled(false); d->exportPdf->setEnabled(false); d->closeFile->setEnabled(false); #if 0 d->encryptDocument->setEnabled(false); #ifndef NDEBUG d->uncompressToDir->setEnabled(false); #endif #endif KToggleAction *fullscreenAction = new KToggleAction(koIcon("view-fullscreen"), i18n("Full Screen Mode"), this); actionCollection()->addAction("view_fullscreen", fullscreenAction); actionCollection()->setDefaultShortcut(fullscreenAction, QKeySequence::FullScreen); connect(fullscreenAction, &QAction::toggled, this, &KoMainWindow::viewFullscreen); d->toggleDockers = new KToggleAction(i18n("Show Dockers"), this); d->toggleDockers->setChecked(true); actionCollection()->addAction("view_toggledockers", d->toggleDockers); connect(d->toggleDockers, &QAction::toggled, this, &KoMainWindow::toggleDockersVisibility); d->toggleDockerTitleBars = new KToggleAction(i18nc("@action:inmenu", "Show Docker Titlebars"), this); KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); d->toggleDockerTitleBars->setChecked(configGroupInterface.readEntry("ShowDockerTitleBars", true)); d->toggleDockerTitleBars->setVisible(false); actionCollection()->addAction("view_toggledockertitlebars", d->toggleDockerTitleBars); connect(d->toggleDockerTitleBars, &QAction::toggled, this, &KoMainWindow::showDockerTitleBars); d->dockWidgetMenu = new KActionMenu(i18n("Dockers"), this); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); d->dockWidgetMenu->setVisible(false); d->dockWidgetMenu->setDelayed(false); // Load list of recent files KSharedConfigPtr configPtr = componentData.config(); d->recent->loadEntries(configPtr->group("RecentFiles")); createMainwindowGUI(); d->mainWindowGuiIsBuilt = true; // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen()); desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreState(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KoMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KoMainWindow::~KoMainWindow() { KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); // The doc and view might still exist (this is the case when closing the window) if (d->rootPart) d->rootPart->removeMainWindow(this); if (d->partToOpen) { d->partToOpen->removeMainWindow(this); delete d->partToOpen; } // safety first ;) setActivePart(0, 0); if (d->rootViews.indexOf(d->activeView) == -1) { delete d->activeView; d->activeView = 0; } while (!d->rootViews.isEmpty()) { delete d->rootViews.takeFirst(); } if(d->noCleanup) return; // We have to check if this was a root document. // This has to be checked from queryClose, too :) if (d->rootPart && d->rootPart->viewCount() == 0) { - //debugMain <<"Destructor. No more views, deleting old doc" << d->rootDoc; - delete d->rootDocument; + //debugMain <<"Destructor. No more views, deleting old doc" << d->rootDocument; + delete d->rootDocument; // FIXME: Why delete here and not only in KoPart? } delete d; } void KoMainWindow::setRootDocument(KoDocument *doc, KoPart *part, bool deletePrevious) { if (d->rootDocument == doc) return; if (d->partToOpen && d->partToOpen->document() != doc) { d->partToOpen->removeMainWindow(this); if (deletePrevious) delete d->partToOpen; } d->partToOpen = 0; //debugMain <<"KoMainWindow::setRootDocument this =" << this <<" doc =" << doc; QList oldRootViews = d->rootViews; d->rootViews.clear(); KoDocument *oldRootDoc = d->rootDocument; KoPart *oldRootPart = d->rootPart; if (oldRootDoc) { oldRootDoc->disconnect(this); oldRootPart->removeMainWindow(this); // Hide all dockwidgets and remember their old state d->dockWidgetVisibilityMap.clear(); foreach(QDockWidget* dockWidget, d->dockWidgetsMap) { d->dockWidgetVisibilityMap.insert(dockWidget, dockWidget->isVisible()); dockWidget->setVisible(false); } d->toggleDockerTitleBars->setVisible(false); d->dockWidgetMenu->setVisible(false); } d->rootDocument = doc; // XXX remove this after the splitting if (!part && doc) { d->rootPart = doc->documentPart(); } else { d->rootPart = part; } if (doc) { d->toggleDockerTitleBars->setVisible(true); d->dockWidgetMenu->setVisible(true); d->m_registeredPart = d->rootPart.data(); KoView *view = d->rootPart->createView(doc, this); setCentralWidget(view); d->rootViews.append(view); view->show(); view->setFocus(); // The addMainWindow has been done already if using openUrl if (!d->rootPart->mainWindows().contains(this)) { d->rootPart->addMainWindow(this); } } bool enable = d->rootDocument != 0 ? true : false; d->showDocumentInfo->setEnabled(enable); d->saveAction->setEnabled(enable); d->saveActionAs->setEnabled(enable); d->importFile->setEnabled(enable); d->exportFile->setEnabled(enable); #if 0 d->encryptDocument->setEnabled(enable); #ifndef NDEBUG d->uncompressToDir->setEnabled(enable); #endif #endif d->printAction->setEnabled(enable); d->printActionPreview->setEnabled(enable); d->sendFileAction->setEnabled(enable); d->exportPdf->setEnabled(enable); d->closeFile->setEnabled(enable); updateCaption(); setActivePart(d->rootPart, doc ? d->rootViews.first() : 0); emit restoringDone(); while(!oldRootViews.isEmpty()) { delete oldRootViews.takeFirst(); } if (oldRootPart && oldRootPart->viewCount() == 0) { //debugMain <<"No more views, deleting old doc" << oldRootDoc; oldRootDoc->clearUndoHistory(); if(deletePrevious) delete oldRootDoc; } if (doc && !d->dockWidgetVisibilityMap.isEmpty()) { foreach(QDockWidget* dockWidget, d->dockWidgetsMap) { dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } if (!d->rootDocument) { statusBar()->setVisible(false); } else { #ifdef Q_OS_MAC statusBar()->setMaximumHeight(28); #endif connect(d->rootDocument, &KoDocument::titleModified, this, &KoMainWindow::slotDocumentTitleModified); } } void KoMainWindow::updateReloadFileAction(KoDocument *doc) { d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KoMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KoMainWindow::addRecentURL(const QUrl &url) { debugMain << "url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KoDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = QStandardPaths::standardLocations(QStandardPaths::TempLocation); foreach (const QString &tmpDir, tmpDirs) { if (path.startsWith(tmpDir)) { ok = false; // it's in the tmp resource break; } } if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); KRecentDirs::add(":OpenDialog", QFileInfo(path).dir().canonicalPath()); } } else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } if (ok) { d->recent->addUrl(url); } saveRecentFiles(); #ifdef HAVE_KACTIVITIES if (!d->activityResource) { d->activityResource = new KActivities::ResourceInstance(winId(), this); } d->activityResource->setUri(url); #endif } } void KoMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = componentData().config(); debugMain << this << " Saving recent files list into config. componentData()=" << componentData().componentName(); d->recent->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start foreach(KMainWindow* window, KMainWindow::memberList()) static_cast(window)->reloadRecentFileList(); } void KoMainWindow::reloadRecentFileList() { KSharedConfigPtr config = componentData().config(); d->recent->loadEntries(config->group("RecentFiles")); } KoPart* KoMainWindow::createPart() const { KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(d->nativeMimeType); QString errorMsg; KoPart *part = entry.createKoPart(&errorMsg); if (!part || !errorMsg.isEmpty()) { return 0; } return part; } void KoMainWindow::updateCaption() { debugMain; if (!d->rootDocument) { updateCaption(QString(), false); } else { QString caption( d->rootDocument->caption() ); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } updateCaption(caption, d->rootDocument->isModified()); if (!rootDocument()->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", d->rootDocument->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KoMainWindow::updateCaption(const QString & caption, bool mod) { debugMain << caption << "," << mod; #ifdef PLAN_ALPHA setCaption(QString("ALPHA %1: %2").arg(PLAN_ALPHA).arg(caption), mod); return; #endif #ifdef PLAN_BETA setCaption(QString("BETA %1: %2").arg(PLAN_BETA).arg(caption), mod); return; #endif #ifdef PLAN_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(PLAN_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KoDocument *KoMainWindow::rootDocument() const { return d->rootDocument; } KoView *KoMainWindow::rootView() const { if (d->rootViews.indexOf(d->activeView) != -1) return d->activeView; return d->rootViews.first(); } bool KoMainWindow::openDocument(const QUrl &url) { if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) { KMessageBox::error(0, i18n("The file %1 does not exist.", url.url())); d->recent->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url); } bool KoMainWindow::openDocument(KoPart *newPart, const QUrl &url) { + if (!newPart) { + return openDocument(url); + } // the part always has a document; the document doesn't know about the part. KoDocument *newdoc = newPart->document(); if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) { newdoc->initEmpty(); //create an empty document setRootDocument(newdoc, newPart); newdoc->setUrl(url); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QString mimetype = (!mime.isValid() || mime.isDefault()) ? newdoc->nativeFormatMimeType() : mime.name(); newdoc->setMimeTypeAfterLoading(mimetype); updateCaption(); return true; } return openDocumentInternal(url, newPart, newdoc); } bool KoMainWindow::openDocumentInternal(const QUrl &url, KoPart *newpart, KoDocument *newdoc) { - debugMain << url.url(); + debugMain << newpart << newdoc << url.url(); if (!newpart) newpart = createPart(); if (!newpart) return false; if (!newdoc) newdoc = newpart->document(); d->firstTime = true; connect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); connect(newdoc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); connect(newdoc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = true; newpart->addMainWindow(this); // used by openUrl bool openRet = (!isImporting()) ? newdoc->openUrl(url) : newdoc->importDocument(url); if (!openRet) { newpart->removeMainWindow(this); delete newdoc; delete newpart; d->openingDocument = false; return false; } updateReloadFileAction(newdoc); KFileItem file(url, newdoc->mimeType(), KFileItem::Unknown); if (!file.isWritable()) { setReadWrite(false); } return true; } // Separate from openDocument to handle async loading (remote URLs) void KoMainWindow::slotLoadCompleted() { debugMain; KoDocument *newdoc = qobject_cast(sender()); KoPart *newpart = newdoc->documentPart(); if (d->rootDocument && d->rootDocument->isEmpty()) { // Replace current empty document setRootDocument(newdoc); } else if (d->rootDocument && !d->rootDocument->isEmpty()) { // Open in a new main window // (Note : could create the main window first and the doc next for this // particular case, that would give a better user feedback...) KoMainWindow *s = newpart->createMainWindow(); s->show(); newpart->removeMainWindow(this); s->setRootDocument(newdoc, newpart); } else { // We had no document, set the new one setRootDocument(newdoc); } slotProgress(-1); disconnect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(newdoc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); disconnect(newdoc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = false; emit loadCompleted(); } void KoMainWindow::slotLoadCanceled(const QString & errMsg) { debugMain; if (!errMsg.isEmpty()) // empty when canceled by user KMessageBox::error(this, errMsg); // ... can't delete the document, it's the one who emitted the signal... KoDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(doc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); disconnect(doc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = false; emit loadCanceled(); } void KoMainWindow::slotSaveCanceled(const QString &errMsg) { debugMain; if (!errMsg.isEmpty()) // empty when canceled by user KMessageBox::error(this, errMsg); slotSaveCompleted(); } void KoMainWindow::slotSaveCompleted() { debugMain; KoDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(doc, &KoDocument::completed, this, &KoMainWindow::slotSaveCompleted); disconnect(doc, &KoDocument::canceled, this, &KoMainWindow::slotSaveCanceled); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } // returns true if we should save, false otherwise. bool KoMainWindow::exportConfirmation(const QByteArray &outputFormat) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); if (!group.readEntry("WantExportConfirmation", true)) { return true; } QMimeType mime = QMimeDatabase().mimeTypeForName(outputFormat); QString comment = mime.isValid() ? mime.comment() : i18n("%1 (unknown file type)", QString::fromLatin1(outputFormat)); // Warn the user int ret; if (!isExporting()) { // File --> Save ret = KMessageBox::warningContinueCancel ( this, i18n("Saving as a %1 may result in some loss of formatting." "

Do you still want to save in this format?", QString("%1").arg(comment)), // in case we want to remove the bold later i18n("Confirm Save"), KStandardGuiItem::save(), KStandardGuiItem::cancel(), "NonNativeSaveConfirmation" ); } else { // File --> Export ret = KMessageBox::warningContinueCancel ( this, i18n("Exporting as a %1 may result in some loss of formatting." "

Do you still want to export to this format?", QString("%1").arg(comment)), // in case we want to remove the bold later i18n("Confirm Export"), KGuiItem(i18n("Export")), KStandardGuiItem::cancel(), "NonNativeExportConfirmation" // different to the one used for Save (above) ); } return (ret == KMessageBox::Continue); } bool KoMainWindow::saveDocument(bool saveas, bool silent, int specialOutputFlag) { if (!d->rootDocument || !d->rootPart) { return true; } bool reset_url; if (d->rootDocument->url().isEmpty()) { emit saveDialogShown(); reset_url = true; saveas = true; } else { reset_url = false; } connect(d->rootDocument, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); connect(d->rootDocument, &KoDocument::completed, this, &KoMainWindow::slotSaveCompleted); connect(d->rootDocument, &KoDocument::canceled, this, &KoMainWindow::slotSaveCanceled); QUrl oldURL = d->rootDocument->url(); QString oldFile = d->rootDocument->localFilePath(); QByteArray _native_format = d->rootDocument->nativeFormatMimeType(); QByteArray oldOutputFormat = d->rootDocument->outputMimeType(); int oldSpecialOutputFlag = d->rootDocument->specialOutputFlag(); QUrl suggestedURL = d->rootDocument->url(); QStringList mimeFilter; QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format); if (!mime.isValid()) // QT5TODO: find if there is no better way to get an object for the default type mime = QMimeDatabase().mimeTypeForName(QStringLiteral("application/octet-stream")); if (specialOutputFlag) mimeFilter = mime.globPatterns(); else mimeFilter = KoFilterManager::mimeFilter(_native_format, KoFilterManager::Export, d->rootDocument->extraNativeMimeTypes()); if (oldOutputFormat.isEmpty() && !d->rootDocument->url().isEmpty()) { // Not been saved yet, but there is a default url so open dialog with this url if (suggestedURL.path() == suggestedURL.fileName()) { // only a filename has been given, so add the default dir KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString path = group.readEntry("SaveDocument"); path += '/' + suggestedURL.fileName(); suggestedURL.setPath(path); suggestedURL.setScheme("file"); } saveas = true; debugMain << "newly created doc, default file name:" << d->rootDocument->url() << "save to:" << suggestedURL; } else if (!mimeFilter.contains(oldOutputFormat) && !isExporting()) { debugMain << "no export filter for" << oldOutputFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = suggestedURL.fileName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name int c = suggestedFilename.lastIndexOf('.'); const QString ext = mime.preferredSuffix(); if (!ext.isEmpty()) { if (c < 0) suggestedFilename += ext; else suggestedFilename = suggestedFilename.left(c) + ext; } else { // current filename extension wrong anyway if (c > 0) { // this assumes that a . signifies an extension, not just a . suggestedFilename = suggestedFilename.left(c); } } suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (d->rootDocument->url().isEmpty() || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument"); dialog.setCaption(i18n("untitled")); dialog.setDefaultDir((isExporting() && !d->lastExportUrl.isEmpty()) ? d->lastExportUrl.toLocalFile() : suggestedURL.toLocalFile()); dialog.setMimeTypeFilters(mimeFilter); QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format); fn.append(mime.preferredSuffix()); newURL = QUrl::fromLocalFile(fn); } } QByteArray outputFormat = _native_format; if (!specialOutputFlag) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(newURL); outputFormat = mime.name().toLatin1(); } if (!isExporting()) justChangingFilterOptions = (newURL == d->rootDocument->url()) && (outputFormat == d->rootDocument->mimeType()) && (specialOutputFlag == oldSpecialOutputFlag); else justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat) && (specialOutputFlag == d->lastExportSpecialOutputFlag); bool bOk = true; if (newURL.isEmpty()) { bOk = false; } // adjust URL before doing checks on whether the file exists. if (specialOutputFlag) { QString fileName = newURL.fileName(); if ( specialOutputFlag== KoDocument::SaveAsDirectoryStore) { // Do nothing } #if 0 else if (specialOutputFlag == KoDocument::SaveEncrypted) { int dot = fileName.lastIndexOf('.'); QString ext = mime.preferredSuffix(); if (!ext.isEmpty()) { if (dot < 0) fileName += ext; else fileName = fileName.left(dot) + ext; } else { // current filename extension wrong anyway if (dot > 0) fileName = fileName.left(dot); } newURL = newURL.adjusted(QUrl::RemoveFilename); newURL.setPath(newURL.path() + fileName); } #endif } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions || d->rootDocument->confirmNonNativeSave(isExporting())) { if (!d->rootDocument->isNativeFormat(outputFormat)) wantToSave = exportConfirmation(outputFormat); } if (wantToSave) { // // Note: // If the user is stupid enough to Export to the current URL, // we do _not_ change this operation into a Save As. Reasons // follow: // // 1. A check like "isExporting() && oldURL == newURL" // doesn't _always_ work on case-insensitive filesystems // and inconsistent behaviour is bad. // 2. It is probably not a good idea to change d->rootDocument->mimeType // and friends because the next time the user File/Save's, // (not Save As) they won't be expecting that they are // using their File/Export settings // // As a bad side-effect of this, the modified flag will not // be updated and it is possible that what is currently on // their screen is not what is stored on disk (through loss // of formatting). But if you are dumb enough to change // mimetype but not the filename, then arguably, _you_ are // the "bug" :) // // - Clarence // d->rootDocument->setOutputMimeType(outputFormat, specialOutputFlag); if (!isExporting()) { // Save As ret = d->rootDocument->saveAs(newURL); if (ret) { debugMain << "Successful Save As!"; addRecentURL(newURL); setReadWrite(true); } else { debugMain << "Failed Save As!"; d->rootDocument->setUrl(oldURL); d->rootDocument->setLocalFilePath(oldFile); d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag); } } else { // Export ret = d->rootDocument->exportDocument(newURL); if (ret) { // a few file dialog convenience things d->lastExportUrl = newURL; d->lastExportedFormat = outputFormat; d->lastExportSpecialOutputFlag = specialOutputFlag; } // always restore output format d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag); } if (silent) // don't let the document change the window caption d->rootDocument->setTitleModified(); } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving bool needConfirm = d->rootDocument->confirmNonNativeSave(false) && !d->rootDocument->isNativeFormat(oldOutputFormat); if (!needConfirm || (needConfirm && exportConfirmation(oldOutputFormat /* not so old :) */)) ) { // be sure d->rootDocument has the correct outputMimeType! if (isExporting() || d->rootDocument->isModified() || d->rootDocument->alwaysAllowSaving()) { ret = d->rootDocument->save(); } if (!ret) { debugMain << "Failed Save!"; d->rootDocument->setUrl(oldURL); d->rootDocument->setLocalFilePath(oldFile); } } else ret = false; } if (!ret && reset_url) d->rootDocument->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(d->rootDocument); updateCaption(); return ret; } void KoMainWindow::closeEvent(QCloseEvent *e) { // If we are in the process of opening a new document, rootDocument() may not have been set yet, // so we must prevent closing to avoid crash. if(d->openingDocument || (rootDocument() && rootDocument()->isLoading())) { e->setAccepted(false); return; } if (queryClose()) { d->deferredClosingEvent = e; if (!d->m_dockerStateBeforeHiding.isEmpty()) { restoreState(d->m_dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if(d->noCleanup) return; setRootDocument(0); if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency foreach(QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KoMainWindow::saveWindowSettings() { KSharedConfigPtr config = componentData().config(); if (d->windowSizeDirty ) { // Save window size into the config file of our componentData // TODO: check if this is ever read again, seems lost over the years debugMain; KConfigGroup mainWindowConfigGroup = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), mainWindowConfigGroup); config->sync(); d->windowSizeDirty = false; } if ( rootDocument() && d->rootPart) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); //debugMain <<"KoMainWindow::closeEvent -> saveMainWindowSettings rootdoc's componentData=" << d->rootPart->componentData().componentName(); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KoMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } bool KoMainWindow::queryClose() { if (rootDocument() == 0) return true; //debugMain <<"KoMainWindow::queryClose() viewcount=" << rootDocument()->viewCount() // << " mainWindowCount=" << rootDocument()->mainWindowCount() << endl; if (!d->forQuit && d->rootPart && d->rootPart->mainwindowCount() > 1) // there are more open, and we are closing just one, so no problem for closing return true; // main doc + internally stored child documents if (d->rootDocument->isModified()) { QString name; if (rootDocument()->documentInfo()) { name = rootDocument()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = rootDocument()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = KMessageBox::warningYesNoCancel(this, i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QString(), KStandardGuiItem::save(), KStandardGuiItem::discard()); switch (res) { case KMessageBox::Yes : { bool isNative = (d->rootDocument->outputMimeType() == d->rootDocument->nativeFormatMimeType()); if (!saveDocument(!isNative)) return false; break; } case KMessageBox::No : rootDocument()->removeAutoSaveFiles(); rootDocument()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; default : // case KMessageBox::Cancel : return false; } } return true; } // Helper method for slotFileNew and slotFileClose void KoMainWindow::chooseNewDocument(InitDocFlags initDocFlags) { KoDocument* doc = rootDocument(); KoPart *newpart = createPart(); KoDocument *newdoc = newpart->document(); - qInfo()<isEmpty())) { KoMainWindow *s = newpart->createMainWindow(); s->show(); newpart->addMainWindow(s); newpart->showStartUpWidget(s); return; } if (doc) { setRootDocument(0); if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; } newpart->addMainWindow(this); newpart->showStartUpWidget(this); } void KoMainWindow::slotFileNew() { chooseNewDocument(InitDocFileNew); } void KoMainWindow::slotFileOpen() { QUrl url; if (!isImporting()) { KoFileDialog dialog(this, KoFileDialog::OpenFile, "OpenDocument"); dialog.setCaption(i18n("Open Document")); dialog.setDefaultDir(qApp->applicationName().contains("karbon") ? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) : QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import)); dialog.setHideNameFilterDetailsOption(); url = QUrl::fromUserInput(dialog.filename()); } else { KoFileDialog dialog(this, KoFileDialog::ImportFile, "OpenDocument"); dialog.setCaption(i18n("Import Document")); dialog.setDefaultDir(qApp->applicationName().contains("karbon") ? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) : QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import)); dialog.setHideNameFilterDetailsOption(); url = QUrl::fromUserInput(dialog.filename()); } if (url.isEmpty()) return; (void) openDocument(url); } -void KoMainWindow::slotFileOpenRecent(const QUrl & url) +void KoMainWindow::slotFileOpenRecent(const QUrl & url, KoPart *part) { // Create a copy, because the original QUrl in the map of recent files in // KRecentFilesAction may get deleted. - (void) openDocument(QUrl(url)); + (void) openDocument(part, QUrl(url)); } void KoMainWindow::slotFileSave() { if (saveDocument()) emit documentSaved(); } void KoMainWindow::slotFileSaveAs() { if (saveDocument(true)) emit documentSaved(); } void KoMainWindow::slotEncryptDocument() { if (saveDocument(false, false, KoDocument::SaveEncrypted)) emit documentSaved(); } void KoMainWindow::slotUncompressToDir() { if (saveDocument(true, false, KoDocument::SaveAsDirectoryStore)) emit documentSaved(); } void KoMainWindow::slotDocumentInfo() { if (!rootDocument()) return; KoDocumentInfo *docInfo = rootDocument()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->rootDocument->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { rootDocument()->setModified(false); } else { rootDocument()->setModified(true); } rootDocument()->setTitleModified(); } delete dlg; } void KoMainWindow::slotFileClose() { if (queryClose()) { saveWindowSettings(); setRootDocument(0); // don't delete this main window when deleting the document if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; chooseNewDocument(InitDocFileClose); } } void KoMainWindow::slotFileQuit() { close(); } void KoMainWindow::slotFilePrint() { if (!rootView()) return; KoPrintJob *printJob = rootView()->createPrintJob(); if (printJob == 0) return; d->applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = rootView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) printJob->startPrinting(KoPrintJob::DeleteWhenDone); else delete printJob; delete printDialog; } void KoMainWindow::slotFilePrintPreview() { if (!rootView()) return; KoPrintJob *printJob = rootView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KoPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); // clazy:exclude=old-style-connect preview->exec(); delete preview; } KoPrintJob* KoMainWindow::exportToPdf() { return exportToPdf(QString()); } KoPrintJob* KoMainWindow::exportToPdf(const QString &_pdfFileName) { if (!rootView()) return 0; KoPageLayout pageLayout; pageLayout = rootView()->pageLayout(); QString pdfFileName = _pdfFileName; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KoDocument* pDoc = rootDocument(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.fileName(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; - qInfo()<url()<url().isValid()<createPdfPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } d->applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); //before printing check if the printer can handle printing if (!printJob->canPrint()) { KMessageBox::error(this, i18n("Cannot export to the specified file")); } printJob->startPrinting(KoPrintJob::DeleteWhenDone); rootView()->setPageLayout(pageLayout); return printJob; } void KoMainWindow::slotConfigureKeys() { QAction* undoAction=0; QAction* redoAction=0; QString oldUndoText; QString oldRedoText; if(currentView()) { //The undo/redo action text is "undo" + command, replace by simple text while inside editor undoAction = currentView()->actionCollection()->action("edit_undo"); redoAction = currentView()->actionCollection()->action("edit_redo"); oldUndoText = undoAction->text(); oldRedoText = redoAction->text(); undoAction->setText(i18n("Undo")); redoAction->setText(i18n("Redo")); } guiFactory()->configureShortcuts(); if(currentView()) { undoAction->setText(oldUndoText); redoAction->setText(oldRedoText); } emit keyBindingsChanged(); } void KoMainWindow::slotConfigureToolbars() { if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); saveMainWindowSettings(componentConfigGroup); } KEditToolBar edit(factory(), this); connect(&edit, &KEditToolBar::newToolBarConfig, this, &KoMainWindow::slotNewToolbarConfig); (void) edit.exec(); } void KoMainWindow::slotNewToolbarConfig() { if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); applyMainWindowSettings(componentConfigGroup); } KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); } void KoMainWindow::slotToolbarToggled(bool toggle) { //debugMain <<"KoMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) bar->show(); else bar->hide(); if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); saveMainWindowSettings(componentConfigGroup); } } else warnMain << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } bool KoMainWindow::toolbarIsVisible(const char *tbName) { QWidget *tb = toolBar(tbName); return !tb->isHidden(); } void KoMainWindow::showToolbar(const char * tbName, bool shown) { QWidget * tb = toolBar(tbName); if (!tb) { warnMain << "KoMainWindow: toolbar " << tbName << " not found."; return; } if (shown) tb->show(); else tb->hide(); // Update the action appropriately foreach(QAction* action, d->toolbarList) { if (action->objectName() != tbName) { //debugMain <<"KoMainWindow::showToolbar setChecked" << shown; static_cast(action)->setChecked(shown); break; } } } void KoMainWindow::viewFullscreen(bool fullScreen) { if (fullScreen) { window()->setWindowState(window()->windowState() | Qt::WindowFullScreen); // set } else { window()->setWindowState(window()->windowState() & ~Qt::WindowFullScreen); // reset } } void KoMainWindow::slotProgress(int value) { QMutexLocker locker(&d->progressMutex); debugMain << value; if (value <= -1 || value >= 100) { if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; } d->firstTime = true; return; } if (d->firstTime || !d->progress) { // The statusbar might not even be created yet. // So check for that first, and create it if necessary QStatusBar *bar = findChild(); if (!bar) { statusBar()->show(); QApplication::sendPostedEvents(this, QEvent::ChildAdded); } if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; } d->progress = new QProgressBar(statusBar()); d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); d->progress->setRange(0, 100); statusBar()->addPermanentWidget(d->progress); d->progress->show(); d->firstTime = false; } if (!d->progress.isNull()) { d->progress->setValue(value); } locker.unlock(); qApp->processEvents(); } void KoMainWindow::setMaxRecentItems(uint _number) { d->recent->setMaxItems(_number); } void KoMainWindow::slotEmailFile() { if (!rootDocument()) return; // Subject = Document file name // Attachment = The current file // Message Body = The current document in HTML export? <-- This may be an option. QString theSubject; QStringList urls; QString fileURL; if (rootDocument()->url().isEmpty() || rootDocument()->isModified()) { //Save the file as a temporary file bool const tmp_modified = rootDocument()->isModified(); QUrl const tmp_url = rootDocument()->url(); QByteArray const tmp_mimetype = rootDocument()->outputMimeType(); // a little open, close, delete dance to make sure we have a nice filename // to use, but won't block windows from creating a new file with this name. QTemporaryFile *tmpfile = new QTemporaryFile(); tmpfile->open(); QString fileName = tmpfile->fileName(); tmpfile->close(); delete tmpfile; QUrl u = QUrl::fromLocalFile(fileName); rootDocument()->setUrl(u); rootDocument()->setModified(true); rootDocument()->setOutputMimeType(rootDocument()->nativeFormatMimeType()); saveDocument(false, true); fileURL = fileName; theSubject = i18n("Document"); urls.append(fileURL); rootDocument()->setUrl(tmp_url); rootDocument()->setModified(tmp_modified); rootDocument()->setOutputMimeType(tmp_mimetype); } else { fileURL = rootDocument()->url().url(); theSubject = i18n("Document - %1", rootDocument()->url().fileName()); urls.append(fileURL); } debugMain << "(" << fileURL << ")"; if (!fileURL.isEmpty()) { KToolInvocation::invokeMailer(QString(), QString(), QString(), theSubject, QString(), //body QString(), urls); // attachments } } void KoMainWindow::slotReloadFile() { KoDocument* pDoc = rootDocument(); if (!pDoc || pDoc->url().isEmpty() || !pDoc->isModified()) return; bool bOk = KMessageBox::questionYesNo(this, i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), i18n("Warning")) == KMessageBox::Yes; if (!bOk) return; QUrl url = pDoc->url(); if (!pDoc->isEmpty()) { saveWindowSettings(); setRootDocument(0); // don't delete this main window when deleting the document if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; } openDocument(url); return; } void KoMainWindow::slotImportFile() { debugMain; d->isImporting = true; slotFileOpen(); d->isImporting = false; } void KoMainWindow::slotExportFile() { debugMain; d->isExporting = true; slotFileSaveAs(); d->isExporting = false; } bool KoMainWindow::isImporting() const { return d->isImporting; } bool KoMainWindow::isExporting() const { return d->isExporting; } void KoMainWindow::setPartToOpen(KoPart *part) { d->partToOpen = part; } KoComponentData KoMainWindow::componentData() const { return d->componentData; } QDockWidget* KoMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; - qInfo()<id()<dockWidgetsMap; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) return 0; d->dockWidgets.push_back(dockWidget); KoDockWidgetTitleBar *titleBar = 0; // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } if (rootDocument()) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; } addDockWidget(side, dockWidget); if (dockWidget->features() & QDockWidget::DockWidgetClosable) { d->dockWidgetMenu->addAction(dockWidget->toggleViewAction()); if (!visible) dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; if (rootDocument()) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); } if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); if (titleBar) { KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); titleBar->setVisible(configGroupInterface.readEntry("ShowDockerTitleBars", true)); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[ factory->id()]; } #ifdef Q_OS_MAC dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, &QDockWidget::dockLocationChanged, this, &KoMainWindow::forceDockTabFonts); return dockWidget; } void KoMainWindow::forceDockTabFonts() { QObjectList chis = children(); for (int i = 0; i < chis.size(); ++i) { if (chis.at(i)->inherits("QTabBar")) { ((QTabBar *)chis.at(i))->setFont(KoDockRegistry::dockFont()); } } } QList KoMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } /*QList KoMainWindow::canvasObservers() const { QList observers; foreach(QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } } return observers; }*/ void KoMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->m_dockerStateBeforeHiding = saveState(); foreach(QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->m_dockerStateBeforeHiding); } } KRecentFilesAction *KoMainWindow::recentAction() const { return d->recent; } KoView* KoMainWindow::currentView() const { // XXX if (d->activeView) { return d->activeView; } else if (!d->rootViews.isEmpty()) { return d->rootViews.first(); } return 0; } void KoMainWindow::newView() { Q_ASSERT((d != 0 && d->activeView && d->activePart && d->activeView->koDocument())); KoMainWindow *mainWindow = d->activePart->createMainWindow(); mainWindow->setRootDocument(d->activeView->koDocument(), d->activePart); mainWindow->show(); } void KoMainWindow::createMainwindowGUI() { if ( isHelpMenuEnabled() && !d->m_helpMenu ) { d->m_helpMenu = new KHelpMenu( this, componentData().aboutData(), true ); KActionCollection *actions = actionCollection(); QAction *helpContentsAction = d->m_helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->m_helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->m_helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->m_helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->m_helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->m_helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } } QString f = xmlFile(); setXMLFile( QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ui/ui_standards.rc")) ); if ( !f.isEmpty() ) setXMLFile( f, true ); else { QString auto_file( componentData().componentName() + "ui.rc" ); setXMLFile( auto_file, true ); } guiFactory()->addClient( this ); } // PartManager void KoMainWindow::removePart( KoPart *part ) { if (d->m_registeredPart.data() != part) { return; } d->m_registeredPart = 0; if ( part == d->m_activePart ) { setActivePart(0, 0); } } void KoMainWindow::setActivePart(KoPart *part, QWidget *widget ) { if (part && d->m_registeredPart.data() != part) { warnMain << "trying to activate a non-registered part!" << part->objectName(); return; // don't allow someone call setActivePart with a part we don't know about } // don't activate twice if ( d->m_activePart && part && d->m_activePart == part && (!widget || d->m_activeWidget == widget) ) return; KoPart *oldActivePart = d->m_activePart; QWidget *oldActiveWidget = d->m_activeWidget; d->m_activePart = part; d->m_activeWidget = widget; if (oldActivePart) { KoPart *savedActivePart = part; QWidget *savedActiveWidget = widget; if ( oldActiveWidget ) { disconnect( oldActiveWidget, &QObject::destroyed, this, &KoMainWindow::slotWidgetDestroyed ); } d->m_activePart = savedActivePart; d->m_activeWidget = savedActiveWidget; } if (d->m_activePart && d->m_activeWidget ) { connect( d->m_activeWidget, &QObject::destroyed, this, &KoMainWindow::slotWidgetDestroyed ); } // Set the new active instance in KGlobal // KGlobal::setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KGlobal::mainComponent()); // old slot called from part manager KoPart *newPart = static_cast(d->m_activePart.data()); if (d->activePart && d->activePart == newPart) { //debugMain <<"no need to change the GUI"; return; } KXMLGUIFactory *factory = guiFactory(); if (d->activeView) { factory->removeClient(d->activeView); unplugActionList("toolbarlist"); qDeleteAll(d->toolbarList); d->toolbarList.clear(); } if (!d->mainWindowGuiIsBuilt) { createMainwindowGUI(); } if (newPart && d->m_activeWidget && d->m_activeWidget->inherits("KoView")) { d->activeView = qobject_cast(d->m_activeWidget); d->activeView->actionCollection()->addAction("view_newview", actionCollection()->action("view_newview")); d->activePart = newPart; //debugMain <<"new active part is" << d->activePart; factory->addClient(d->activeView); // Position and show toolbars according to user's preference setAutoSaveSettings(newPart->componentData().componentName(), false); KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); const bool showDockerTitleBar = configGroupInterface.readEntry("ShowDockerTitleBars", true); foreach (QDockWidget *wdg, d->dockWidgets) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { if (wdg->titleBarWidget()) { wdg->titleBarWidget()->setVisible(showDockerTitleBar); } wdg->setVisible(true); } } // Create and plug toolbar list for Settings menu foreach(QWidget* it, factory->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { KToggleAction * act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, &QAction::toggled, this, &KoMainWindow::slotToolbarToggled); act->setChecked(!toolBar->isHidden()); d->toolbarList.append(act); } else warnMain << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", d->toolbarList); } else { d->activeView = 0; d->activePart = 0; } if (d->activeView) { d->activeView->guiActivateEvent(true); } } void KoMainWindow::slotWidgetDestroyed() { debugMain; if ( static_cast( sender() ) == d->m_activeWidget ) setActivePart(0, 0); //do not remove the part because if the part's widget dies, then the //part will delete itself anyway, invoking removePart() in its destructor } void KoMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) { updateCaption(caption, mod); updateReloadFileAction(d->rootDocument); } void KoMainWindow::showDockerTitleBars(bool show) { foreach (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(show); } } KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); configGroupInterface.writeEntry("ShowDockerTitleBars", show); } diff --git a/src/libs/main/KoMainWindow.h b/src/libs/main/KoMainWindow.h index f91d4587..2c132528 100644 --- a/src/libs/main/KoMainWindow.h +++ b/src/libs/main/KoMainWindow.h @@ -1,457 +1,457 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 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. */ #ifndef KOMAINWINDOW_H #define KOMAINWINDOW_H #include "komain_export.h" #include //#include //#include class KoMainWindowPrivate; class KoDocument; class KoPart; class KoView; class KoPrintJob; class KoDockFactoryBase; class KRecentFilesAction; class KoComponentData; class QDockWidget; struct KoPageLayout; // Calligra class but not in main module class QUrl; /** * @brief Main window for a Calligra application * * This class is used to represent a main window * of a Calligra component. Each main window contains * a menubar and some toolbars. * * @note This class does NOT need to be subclassed in your application. */ class KOMAIN_EXPORT KoMainWindow : public KXmlGuiWindow//, public KoCanvasSupervisor { Q_OBJECT public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KoMainWindow(const QByteArray &nativeMimeType, const KoComponentData &instance); /** * Destructor. */ virtual ~KoMainWindow(); // If noCleanup is set, KoMainWindow will not delete the root document // or part manager on destruction. void setNoCleanup(bool noCleanup); /** * Called when a document is assigned to this mainwindow. * This creates a view for this document, makes it the active part, etc. */ void setRootDocument(KoDocument *doc, KoPart *part = 0, bool deletePrevious = true); /** * This is used to handle the document used at start up before it actually * added as root document. */ void setPartToOpen(KoPart *part); /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); KoComponentData componentData() const; void setComponentData(const KoComponentData &componentData); /** * Retrieves the document that is displayed in the mainwindow. */ KoDocument *rootDocument() const; KoView *rootView() const; /** * The application should call this to show or hide a toolbar. * It also takes care of the corresponding action in the settings menu. */ void showToolbar(const char * tbName, bool shown); /** * @return TRUE if the toolbar @p tbName is visible */ bool toolbarIsVisible(const char *tbName); /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. */ void addRecentURL(const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ bool openDocument(const QUrl &url); /** * Load the URL into this document (and make it root doc after loading) * * Special method for KoApplication::start, don't use. */ bool openDocument(KoPart *newPart, const QUrl &url); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool mod); void updateReloadFileAction(KoDocument *doc); void setReadWrite(bool readwrite); /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; // QList canvasObservers() const; Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signals is emitted before the save dialog is shown void saveDialogShown(); /// This signal is emitted right after the docker states have been successfully restored from config void restoringDone(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted when this windows has canceled loading of a document. void loadCanceled(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); public Q_SLOTS: /** * Slot for eMailing the document using KMail * * This is a very simple extension that will allow any document * that is currently being edited to be emailed using KMail. */ void slotEmailFile(); /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpen(); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ - void slotFileOpenRecent(const QUrl &); + void slotFileOpenRecent(const QUrl &, KoPart *part=nullptr); /** * Saves the current document with the current name. */ void slotFileSave(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); /** * Prints the actual document. */ void slotFilePrint(); void slotFilePrintPreview(); KoPrintJob* exportToPdf(const QString &pdfFileName); KoPrintJob* exportToPdf(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes the document. */ void slotFileClose(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure key bindings. */ void slotConfigureKeys(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); /** * Toggle full screen on/off. */ void viewFullscreen(bool fullScreen); /** * Toggle docker titlebars on/off. */ void showDockerTitleBars(bool show); /** * Reload file */ void slotReloadFile(); /** * File --> Import * * This will call slotFileOpen(). To differentiate this from an ordinary * call to slotFileOpen() call isImporting(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). To differentiate this from an ordinary * call to slotFileSaveAs() call isExporting(). */ void slotExportFile(); void slotEncryptDocument(); void slotUncompressToDir(); void slotProgress(int value); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Saves the document, asking for a filename if necessary. * * @param saveas if set to TRUE the user is always prompted for a filename * * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. * * @param specialOutputFlag set to enums defined in KoDocument if save to special output format * * @return TRUE on success, false on error or cancel * (don't display anything in this case, the error dialog box is also implemented here * but restore the original URL in slotFileSaveAs) */ bool saveDocument(bool saveas = false, bool silent = false, int specialOutputFlag = 0); private: /** * This setting indicates who is calling chooseNewDocument. * Usually the app will want to * - show the template dialog with 'everything' if InitDocAppStarting, InitDocFileClose or InitDocEmbedded * - show the template dialog with 'templates only' if InitDocFileNew * - create an empty document with default settings if InitDocEmpty */ enum InitDocFlags { /*InitDocAppStarting, */ InitDocFileNew, InitDocFileClose /*, InitDocEmbedded, InitDocEmpty*/ }; /// Helper method for slotFileNew and slotFileClose void chooseNewDocument(InitDocFlags initDocFlags); /** * Create a new empty document. */ KoPart* createPart() const; void closeEvent(QCloseEvent * e); void resizeEvent(QResizeEvent * e); /** * Ask user about saving changes to the document upon exit. */ bool queryClose(); bool openDocumentInternal(const QUrl &url, KoPart *newpart = 0, KoDocument *newdoc = 0); /** * Returns whether or not the current slotFileSave[As]() or saveDocument() * call is actually an export operation (like File --> Export). * * If this is true, you must call KoDocument::export() instead of * KoDocument::save() or KoDocument::saveAs(), in any reimplementation of * saveDocument(). */ bool isExporting() const; /** * Returns whether or not the current slotFileOpen() or openDocument() * call is actually an import operation (like File --> Import). * * If this is true, you must call KoDocument::import() instead of * KoDocument::openUrl(), in any reimplementation of openDocument() or * openDocumentInternal(). */ bool isImporting() const; KRecentFilesAction *recentAction() const; private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); /** * Slot to create a new view for the currently activate @ref #koDocument. */ virtual void newView(); // --------------------- PartManager private: friend class KoPart; /** * Removes a part from the manager (this does not delete the object) . * * Sets the active part to 0 if @p part is the activePart() . */ virtual void removePart( KoPart *part ); /** * Sets the active part. * * The active part receives activation events. * * @p widget can be used to specify which widget was responsible for the activation. * This is important if you have multiple views for a document/part , like in KOffice . */ virtual void setActivePart(KoPart *part, QWidget *widget); private Q_SLOTS: /** * @internal */ void slotWidgetDestroyed(); void slotDocumentTitleModified(const QString &caption, bool mod); // --------------------- PartManager private: void createMainwindowGUI(); /** * Asks the user if they really want to save the document. * Called only if outputFormat != nativeFormat. * * @return true if the document should be saved */ bool exportConfirmation(const QByteArray &outputFormat); void saveWindowSettings(); // retrieve the current KoView KoView* currentView() const; private: KoMainWindowPrivate * const d; }; #endif diff --git a/src/libs/main/KoPart.cpp b/src/libs/main/KoPart.cpp index 91aaa011..eb31105d 100644 --- a/src/libs/main/KoPart.cpp +++ b/src/libs/main/KoPart.cpp @@ -1,283 +1,286 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * * 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. */ // clazy:excludeall=qstring-arg #include "KoPart.h" #include "KoApplication.h" #include "KoMainWindow.h" #include "KoDocument.h" #include "KoView.h" #include "KoFilterManager.h" #include //#include //#include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_DBUS #include #include "KoPartAdaptor.h" #endif class Q_DECL_HIDDEN KoPart::Private { public: Private(const KoComponentData &componentData_, KoPart *_parent) : parent(_parent) , document(0) , componentData(componentData_) { } ~Private() { /// FIXME ok, so this is obviously bad to leave like this // For now, this is undeleted, but only to avoid an odd double // delete condition. Until that's discovered, we'll need this // to avoid crashes in Gemini //delete canvasItem; } KoPart *parent; QList views; QList mainWindows; - KoDocument *document; + QPointer document; QList documents; QString templatesResourcePath; KoComponentData componentData; }; KoPart::KoPart(const KoComponentData &componentData, QObject *parent) : QObject(parent) , d(new Private(componentData, this)) { #ifndef QT_NO_DBUS new KoPartAdaptor(this); QDBusConnection::sessionBus().registerObject('/' + objectName(), this); #endif } KoPart::~KoPart() { // Tell our views that the document is already destroyed and // that they shouldn't try to access it. foreach(KoView *view, views()) { view->setDocumentDeleted(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } + // normally document is deleted in KoMainWindow (why?) + // but if not, we delete it here + delete d->document; delete d; } KoComponentData KoPart::componentData() const { return d->componentData; } void KoPart::setDocument(KoDocument *document) { Q_ASSERT(document); d->document = document; } KoDocument *KoPart::document() const { return d->document; } KoView *KoPart::createView(KoDocument *document, QWidget *parent) { KoView *view = createViewInstance(document, parent); addView(view, document); if (!d->documents.contains(document)) { d->documents.append(document); } return view; } void KoPart::addView(KoView *view, KoDocument *document) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } if (!d->documents.contains(document)) { d->documents.append(document); } view->updateReadWrite(document->isReadWrite()); if (d->views.size() == 1) { KoApplication *app = qobject_cast(qApp); if (0 != app) { emit app->documentOpened('/'+objectName()); } } } void KoPart::removeView(KoView *view) { d->views.removeAll(view); if (d->views.isEmpty()) { KoApplication *app = qobject_cast(qApp); if (0 != app) { emit app->documentClosed('/'+objectName()); } } } QList KoPart::views() const { return d->views; } int KoPart::viewCount() const { return d->views.count(); } QGraphicsItem *KoPart::createCanvasItem(KoDocument *document) { Q_UNUSED(document) return 0; /* KoView *view = createView(document); QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(); QWidget *canvasController = view->findChild(); proxy->setWidget(canvasController); return proxy;*/ } void KoPart::addMainWindow(KoMainWindow *mainWindow) { if (d->mainWindows.indexOf(mainWindow) == -1) { debugMain <<"mainWindow" << (void*)mainWindow <<"added to doc" << this; d->mainWindows.append(mainWindow); } } void KoPart::removeMainWindow(KoMainWindow *mainWindow) { debugMain <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList& KoPart::mainWindows() const { return d->mainWindows; } int KoPart::mainwindowCount() const { return d->mainWindows.count(); } KoMainWindow *KoPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KoMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } void KoPart::openExistingFile(const QUrl &url) { QApplication::setOverrideCursor(Qt::BusyCursor); d->document->openUrl(url); d->document->setModified(false); QApplication::restoreOverrideCursor(); } void KoPart::openTemplate(const QUrl &url) { QApplication::setOverrideCursor(Qt::BusyCursor); bool ok = d->document->loadNativeFormat(url.toLocalFile()); d->document->setModified(false); d->document->undoStack()->clear(); if (ok) { QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); d->document->setMimeTypeAfterLoading(mimeType); d->document->resetURL(); d->document->setEmpty(); } else { d->document->showLoadingErrorDialog(); d->document->initEmpty(); } QApplication::restoreOverrideCursor(); } void KoPart::addRecentURLToAllMainWindows(const QUrl &url) { // Add to recent actions list in our mainWindows foreach(KoMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KoPart::setTemplatesResourcePath(const QString &templatesResourcePath) { Q_ASSERT(!templatesResourcePath.isEmpty()); Q_ASSERT(templatesResourcePath.endsWith(QLatin1Char('/'))); d->templatesResourcePath = templatesResourcePath; } QString KoPart::templatesResourcePath() const { return d->templatesResourcePath; } diff --git a/src/libs/ui/welcome/WelcomeView.cpp b/src/libs/ui/welcome/WelcomeView.cpp index 50429b42..81a1c022 100644 --- a/src/libs/ui/welcome/WelcomeView.cpp +++ b/src/libs/ui/welcome/WelcomeView.cpp @@ -1,321 +1,324 @@ /* This file is part of the KDE project Copyright (C) 2017 Dag Andersen 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. */ // clazy:excludeall=qstring-arg #include "WelcomeView.h" #include "kptcommand.h" #include "kptdebug.h" #include "Help.h" #include #include #include #include #include #include #include #include #include #include #include #include const QLoggingCategory &PLANWELCOME_LOG() { static const QLoggingCategory category("calligra.plan.welcome"); return category; } #define debugWelcome qCDebug(PLANWELCOME_LOG) #define warnWelcome qCWarning(PLANWELCOME_LOG) #define errorWelcome qCCritical(PLANWELCOME_LOG) namespace KPlato { class RecentFilesModel : public QStringListModel { public: RecentFilesModel(QObject *parent = 0); Qt::ItemFlags flags(const QModelIndex &idx) const; QVariant data(const QModelIndex &idx, int role) const; }; RecentFilesModel::RecentFilesModel(QObject *parent) : QStringListModel(parent) { } Qt::ItemFlags RecentFilesModel::flags(const QModelIndex &idx) const { Qt::ItemFlags f = (QStringListModel::flags(idx) & ~Qt::ItemIsEditable); return f; } QVariant RecentFilesModel::data(const QModelIndex &idx, int role) const { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(QStringLiteral("document-open")); break; case Qt::FontRole: break; default: break; } return QStringListModel::data(idx, role); } //----------------------------------- WelcomeView::WelcomeView(KoPart *part, KoDocument *doc, QWidget *parent) : ViewBase(part, doc, parent) , m_projectdialog(0) , m_filedialog(0) { widget.setupUi(this); widget.recentProjects->setBackgroundRole(QPalette::Midlight); Help::add(widget.newProjectBtn, xi18nc("@info:whatsthis", "Create a new project" "" "Creates a new project with default values defined in" " Settings." "Opens the project dialog" " so you can define project specific properties like" " Project Name," " Target Start" " and - End times." "More..." "", Help::page("Manual/Creating_a_Project"))); Help::add(widget.createResourceFileBtn, xi18nc("@info:whatsthis", "Shared resources" "" "Create a shared resources file." "This enables you to only create your resources once," " you just refer to your resources file when you create a new project." "These resources can then be shared between projects" " to avoid overbooking resources across projects." "Shared resources must be defined in a separate file." "More..." "", Help::page("Manual/Managing_Resources"))); Help::add(widget.recentProjects, xi18nc("@info:whatsthis", "Recent Projects" "" "A list of the 10 most recent project files opened." "" "This enables you to quickly open projects you have worked on recently." "")); Help::add(widget.introductionBtn, xi18nc("@info:whatsthis", "Introduction to <application>Plan</application>" "" "These introductory pages gives you hints and tips on what" " you can use Plan for, and how to use it." "")); Help::add(widget.contextHelp, xi18nc("@info:whatsthis", "Context help" "" "Help is available many places using What's This." "It is activated using the menu entry Help->What's this?" " or the keyboard shortcut Shift+F1." "" "In dialogs it is available via the ? in the dialog title bar." "" "If you see More... in the text," " pressing it will display more information from online resources in your browser." "", Help::page("Manual/Context_Help"))); m_model = new RecentFilesModel(this); widget.recentProjects->setModel(m_model); widget.recentProjects->setSelectionMode(QAbstractItemView::SingleSelection); setupGui(); connect(widget.newProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotNewProject); connect(widget.createResourceFileBtn, &QAbstractButton::clicked, this, &WelcomeView::slotCreateResourceFile); connect(widget.openProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotOpenProject); connect(widget.introductionBtn, &QAbstractButton::clicked, this, &WelcomeView::showIntroduction); connect(widget.recentProjects, &QAbstractItemView::activated, this, &WelcomeView::slotRecentFileSelected); } WelcomeView::~WelcomeView() { debugWelcome; } void WelcomeView::setRecentFiles(const QStringList &files) { QStringList lst; for (const QString &s : files) { lst.prepend(s); } m_model->setStringList(lst); } void WelcomeView::updateReadWrite(bool /*readwrite */) { } void WelcomeView::setGuiActive(bool activate) { debugPlan<isEmpty() ? koDocument()->documentPart() : nullptr; if (url.isValid()) { - emit recentProject(url); + emit recentProject(url, part); emit finished(); } } } void WelcomeView::slotContextMenuRequested(const QModelIndex &/*index*/, const QPoint& /*pos */) { //debugPlan<show(); m_projectdialog->raise(); m_projectdialog->activateWindow(); } } void WelcomeView::slotProjectEditFinished(int result) { qDebug()<(sender()); if (dia == 0) { return; } if (result == QDialog::Accepted) { MacroCommand *cmd = dia->buildCommand(); if (cmd) { cmd->execute(); delete cmd; koDocument()->setModified(true); } emit projectCreated(); emit selectDefaultView(); emit finished(); } dia->deleteLater(); } void WelcomeView::slotCreateResourceFile() { QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "templates/.source/SharedResources.plant"); emit openTemplate(QUrl::fromUserInput(file)); emit finished(); } void WelcomeView::slotOpenProject() { if (m_projectdialog) { qWarning()<mimeFilter(KoFilterManager::Import)); filedialog.setHideNameFilterDetailsOption(); + KoPart *part = koDocument()->isEmpty() ? koDocument()->documentPart() : nullptr; QUrl url = QUrl::fromUserInput(filedialog.filename()); - if (!url.isEmpty() && mainWindow()->openDocument(url)) { + if (!url.isEmpty() && mainWindow()->openDocument(part, url)) { emit finished(); } } } void WelcomeView::slotOpenFileFinished(int result) { KoFileDialog *dia = qobject_cast(sender()); if (dia == 0) { return; } if (result == QDialog::Accepted) { QUrl url = QUrl::fromUserInput(dia->filename()); - if (!url.isEmpty() && mainWindow()->openDocument(url)) { + KoPart *part = koDocument()->isEmpty() ? nullptr : koDocument()->documentPart(); + if (!url.isEmpty() && mainWindow()->openDocument(part, url)) { emit finished(); } } dia->deleteLater(); } void WelcomeView::slotLoadSharedResources(const QString &file, const QUrl &projects, bool loadProjectsAtStartup) { QUrl url(file); if (url.scheme().isEmpty()) { url.setScheme("file"); } if (url.isValid()) { emit loadSharedResources(url, loadProjectsAtStartup ? projects :QUrl()); } } } // namespace KPlato diff --git a/src/libs/ui/welcome/WelcomeView.h b/src/libs/ui/welcome/WelcomeView.h index d96f4dc7..7eb6d993 100644 --- a/src/libs/ui/welcome/WelcomeView.h +++ b/src/libs/ui/welcome/WelcomeView.h @@ -1,102 +1,102 @@ /* This file is part of the KDE project Copyright (C) 2017 Dag Andersen 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. */ #ifndef WELCOMEVIEW_H #define WELCOMEVIEW_H #include "planui_export.h" #include "kptviewbase.h" #include "ui_WelcomeView.h" #include "kptmainprojectdialog.h" #include class KoDocument; class QUrl; class QItemSelecteion; namespace KPlato { class RecentFilesModel; class PLANUI_EXPORT WelcomeView : public ViewBase { Q_OBJECT public: WelcomeView(KoPart *part, KoDocument *doc, QWidget *parent); ~WelcomeView(); void setRecentFiles(const QStringList &files); void setupGui(); virtual void updateReadWrite(bool readwrite); KoPrintJob *createPrintJob(); public Q_SLOTS: /// Activate/deactivate the gui virtual void setGuiActive(bool activate); Q_SIGNALS: void newProject(); void openProject(); - void recentProject(const QUrl &file); + void recentProject(const QUrl &file, KoPart *part); void showIntroduction(); void selectDefaultView(); void loadSharedResources(const QUrl &url, const QUrl &projects); void openExistingFile(const QUrl &url); void projectCreated(); void finished(); void openTemplate(QUrl); protected: void updateActionsEnabled( bool on = true); private Q_SLOTS: void slotContextMenuRequested(const QModelIndex &index, const QPoint& pos); void slotRecentFileSelected(const QModelIndex &idx); void slotEnableActions(bool on); void slotNewProject(); void slotOpenProject(); void slotLoadSharedResources(const QString &file, const QUrl &projects, bool loadProjectsAtStartup); void slotProjectEditFinished(int result); void slotOpenFileFinished(int result); void slotCreateResourceFile(); private: Ui::WelcomeView widget; RecentFilesModel *m_model; QPointer m_projectdialog; QPointer m_filedialog; }; } //KPlato namespace #endif