diff --git a/src/workpackage/part.cpp b/src/workpackage/part.cpp index 65f443a6..2d31e0ca 100644 --- a/src/workpackage/part.cpp +++ b/src/workpackage/part.cpp @@ -1,845 +1,845 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2004 - 2009 Dag Andersen Copyright (C) 2006 Raphael Langerhorst Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2007 - 2009, 2012 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 "part.h" #include "view.h" #include "factory.h" #include "mainwindow.h" #include "workpackage.h" #include "KPlatoXmlLoader.h" //NB! #include "kptglobal.h" #include "kptnode.h" #include "kptproject.h" #include "kpttask.h" #include "kptdocuments.h" #include "kptcommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include "debugarea.h" using namespace KPlato; namespace KPlatoWork { //------------------------------- DocumentChild::DocumentChild( WorkPackage *parent) : QObject( parent ), m_doc( 0 ), m_type( Type_Unknown ), m_copy( false ), m_process( 0 ), m_editor( 0 ), m_editormodified( false ), m_filemodified( false ), m_fileSystemWatcher(new QFileSystemWatcher(this)) { } // DocumentChild::DocumentChild( KParts::ReadWritePart *editor, const QUrl &url, const Document *doc, Part *parent) // : KoDocumentChild( parent ), // m_doc( doc ), // m_type( Type_Unknown ), // m_copy( true ), // m_process( 0 ), // m_editor( editor ), // m_editormodified( false ), // m_filemodified( false ) // { // setFileInfo( url ); // if ( dynamic_cast( editor ) ) { // debugPlanWork<<"Creating Calligra doc"; // m_type = Type_Calligra; // connect( static_cast( editor ), SIGNAL(modified(bool)), this, SLOT(setModified(bool)) ); // } else { // debugPlanWork<<"Creating KParts doc"; // m_type = Type_KParts; // slotUpdateModified(); // } // } DocumentChild::~DocumentChild() { debugPlanWork; disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty); m_fileSystemWatcher->removePath( filePath() ); if ( m_type == Type_Calligra || m_type == Type_KParts ) { delete m_editor; } } WorkPackage *DocumentChild::parentPackage() const { return static_cast( parent() ); } void DocumentChild::setFileInfo( const QUrl &url ) { m_fileinfo.setFile( url.path() ); //debugPlanWork<addPath( filePath() ); } void DocumentChild::setModified( bool mod ) { debugPlanWork<isModified() != m_editormodified ) ) { setModified( m_editor->isModified() ); } QTimer::singleShot( 500, this, &DocumentChild::slotUpdateModified ); } bool DocumentChild::setDoc( const Document *doc ) { Q_ASSERT ( m_doc == 0 ); if ( isOpen() ) { KMessageBox::error( 0, i18n( "Document is already open:
%1", doc->url().url() ) ); return false; } m_doc = doc; QUrl url; if ( parentPackage()->newDocuments().contains( doc ) ) { url = parentPackage()->newDocuments().value( doc ); Q_ASSERT( url.isValid() ); parentPackage()->removeNewDocument( doc ); } else if ( doc->sendAs() == Document::SendAs_Copy ) { url = parentPackage()->extractFile( doc ); if ( url.url().isEmpty() ) { KMessageBox::error( 0, i18n( "Could not extract document from storage:
%1", doc->url().url() ) ); return false; } m_copy = true; } else { url = doc->url(); } if ( ! url.isValid() ) { KMessageBox::error( 0, i18n( "Invalid URL:
%1", url.url() ) ); return false; } setFileInfo( url ); return true; } bool DocumentChild::openDoc( const Document *doc, KoStore *store ) { Q_ASSERT ( m_doc == 0 ); if ( isOpen() ) { KMessageBox::error( 0, i18n( "Document is already open:
%1", doc->url().path() ) ); return false; } m_doc = doc; QUrl url; if ( doc->sendAs() == Document::SendAs_Copy ) { url = parentPackage()->extractFile( doc, store ); if ( url.url().isEmpty() ) { KMessageBox::error( 0, i18n( "Could not extract document from storage:
%1", doc->url().path() ) ); return false; } m_copy = true; } else { url = doc->url(); } if ( ! url.isValid() ) { KMessageBox::error( 0, i18n( "Invalid URL:
%1", url.url() ) ); return false; } setFileInfo( url ); return true; } bool DocumentChild::editDoc() { Q_ASSERT( m_doc != 0 ); debugPlanWork<<"file:"< %1", m_doc->url().path() ) ); return false; } if ( ! m_fileinfo.exists() ) { KMessageBox::error( 0, i18n( "File does not exist:
%1", fileName() ) ); return false; } QUrl filename = QUrl::fromLocalFile( filePath() ); const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl( filename ); KService::Ptr service = KMimeTypeTrader::self()->preferredService( mimetype.name() ); bool editing = startProcess( service, filename ); if ( editing ) { m_type = Type_Other; // FIXME: try to be more specific } return editing; } bool DocumentChild::startProcess( KService::Ptr service, const QUrl &url ) { QStringList args; QList files; if ( url.isValid() ) { files << url; } if ( service ) { KIO::DesktopExecParser parser(*service, files); parser.setUrlsAreTempFiles(false); args = parser.resultingArguments(); } else { QList list; QPointer dlg = new KOpenWithDialog( list, i18n("Edit with:"), QString(), 0 ); if ( dlg->exec() == QDialog::Accepted && dlg ){ args << dlg->text(); } if ( args.isEmpty() ) { debugPlanWork<<"No executable selected"; return false; } args << url.url(); delete dlg; } debugPlanWork<setProgram( args ); connect( m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotEditFinished(int,QProcess::ExitStatus)) ); connect( m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(slotEditError(QProcess::ProcessError)) ); m_process->start(); //debugPlanWork<pid()<program(); return true; } bool DocumentChild::isModified() const { return m_editormodified; } bool DocumentChild::isFileModified() const { return m_filemodified; } void DocumentChild::slotEditFinished( int /*par*/, QProcess::ExitStatus ) { //debugPlanWork<deleteLater(); m_process = 0; } else debugPlanWork<<"Error="<removePath( filePath() ); bool ok = false; bool wasmod = m_filemodified; if ( m_type == Type_Calligra || m_type == Type_KParts ) { if ( m_editor->isModified() ) { ok = m_editor->save(); // hmmmm } else { ok = true; } } else if ( m_type == Type_Other ) { if ( isOpen() ) { warnPlanWork<<"External editor open"; } ok = true; } else { errorPlanWork<<"Unknown document type"; } if ( ok ) { debugPlanWork<<"Add to store:"<addLocalFile( filePath(), fileName() ); m_filemodified = false; if ( wasmod != m_filemodified ) { emit fileModified( m_filemodified ); } } m_fileSystemWatcher->addPath( filePath() ); return ok; } //------------------------------------ Part::Part( QWidget *parentWidget, QObject *parent, const QVariantList & /*args*/ ) : KParts::ReadWritePart( parent ), m_xmlLoader(), m_modified( false ), m_loadingFromProjectStore( false ), m_undostack( new KUndo2QStack( this ) ) { debugPlanWork; setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName()); if ( isReadWrite() ) { setXMLFile( "calligraplanwork.rc" ); } else { setXMLFile( "calligraplanwork_readonly.rc" ); } View *v = new View( this, parentWidget, actionCollection() ); setWidget( v ); connect( v, &View::viewDocument, this, &Part::viewWorkpackageDocument ); loadWorkPackages(); connect( m_undostack, &KUndo2QStack::cleanChanged, this, &Part::setDocumentClean ); } Part::~Part() { debugPlanWork; // m_config.save(); qDeleteAll( m_packageMap ); } void Part::addCommand( KUndo2Command *cmd ) { if ( cmd ) { m_undostack->push( cmd ); } } bool Part::setWorkPackage( WorkPackage *wp, KoStore *store ) { //debugPlanWork; QString id = wp->id(); if ( m_packageMap.contains( id ) ) { if ( KMessageBox::warningYesNo( 0, i18n("

The work package already exists in the projects store.

" "

Project: %1
Task: %2

" "

Do you want to update the existing package with data from the new?

", wp->project()->name(), wp->node()->name()) ) == KMessageBox::No ) { delete wp; return false; } m_packageMap[ id ]->merge( this, wp, store ); delete wp; return true; } wp->setFilePath( m_loadingFromProjectStore ? wp->fileName( this ) : localFilePath() ); m_packageMap[ id ] = wp; if ( ! m_loadingFromProjectStore ) { wp->saveToProjects( this ); } connect( wp->project(), SIGNAL(projectChanged()), wp, SLOT(projectChanged()) ); connect ( wp, SIGNAL(modified(bool)), this, SLOT(setModified(bool)) ); emit workPackageAdded( wp, indexOf( wp ) ); connect(wp, &WorkPackage::saveWorkPackage, this, &Part::saveWorkPackage); return true; } void Part::removeWorkPackage( Node *node, MacroCommand *m ) { - //debugPlanWork<name(); + debugPlanWork<name(); WorkPackage *wp = findWorkPackage( node ); if ( wp == 0 ) { KMessageBox::error( 0, i18n("Remove failed. Cannot find work package") ); return; } PackageRemoveCmd *cmd = new PackageRemoveCmd( this, wp, kundo2_i18n( "Remove work package" ) ); if ( m ) { m->addCommand( cmd ); } else { addCommand( cmd ); } } void Part::removeWorkPackages( const QList &nodes ) { - //debugPlanWork<name(); + debugPlanWork<isEmpty() ) { delete m; } else { addCommand( m ); } } void Part::removeWorkPackage( WorkPackage *wp ) { //debugPlanWork; int row = indexOf( wp ); if (row >= 0) { const QList &lst = m_packageMap.keys(); const QString &key = lst.value(row); m_packageMap.remove(key); emit workPackageRemoved(wp, row); } } void Part::addWorkPackage( WorkPackage *wp ) { //debugPlanWork; QString id = wp->id(); Q_ASSERT( ! m_packageMap.contains( id ) ); m_packageMap[ id ] = wp; emit workPackageAdded( wp, indexOf( wp ) ); } bool Part::loadWorkPackages() { m_loadingFromProjectStore = true; const QStringList lst = KoResourcePaths::findAllResources( "projects", "*.planwork", KoResourcePaths::Recursive | KoResourcePaths::NoDuplicates ); debugPlanWork<%1" , file ) ); } } m_loadingFromProjectStore = false; return true; } bool Part::loadNativeFormatFromStore(const QString& file) { debugPlanWork<bad()) { KMessageBox::error( 0, i18n("Not a valid work package file:
%1", file) ); delete store; QApplication::restoreOverrideCursor(); return false; } const bool success = loadNativeFormatFromStoreInternal(store); delete store; return success; } bool Part::loadNativeFormatFromStoreInternal(KoStore * store) { if (store->hasFile("root")) { KoXmlDocument doc; bool ok = loadAndParse(store, "root", doc); if (ok) { ok = loadXML(doc, store); } if (!ok) { QApplication::restoreOverrideCursor(); return false; } } else { errorPlanWork << "ERROR: No maindoc.xml" << endl; KMessageBox::error( 0, i18n("Invalid document. The document does not contain 'maindoc.xml'.") ); QApplication::restoreOverrideCursor(); return false; } // if (store->hasFile("documentinfo.xml")) { // KoXmlDocument doc; // if (oldLoadAndParse(store, "documentinfo.xml", doc)) { // d->m_docInfo->load(doc); // } // } else { // //debugPlanWork <<"cannot open document info"; // delete d->m_docInfo; // d->m_docInfo = new KoDocumentInfo(this); // } bool res = completeLoading(store); QApplication::restoreOverrideCursor(); return res; } bool Part::loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc) { //debugPlanWork <<"Trying to open" << filename; if (!store->open(filename)) { warnPlanWork << "Entry " << filename << " not found!"; KMessageBox::error( 0, i18n("Failed to open file: %1", filename) ); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errorPlanWork << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; KMessageBox::error( 0, i18n("Parsing error in file '%1' at line %2, column %3
Error message: %4", filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0) ) ); return false; } return true; } bool Part::loadXML( const KoXmlDocument &document, KoStore* store ) { debugPlanWork; QString value; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlanWork << "No mime type specified!" << endl; KMessageBox::error( 0, i18n( "Invalid document. No mimetype specified." ) ); return false; } else if ( value == "application/x-vnd.kde.kplato.work" ) { return loadKPlatoXML( document, store ); } else if ( value != "application/x-vnd.kde.plan.work" ) { errorPlanWork << "Unknown mime type " << value; KMessageBox::error( 0, i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value ) ); return false; } QString syntaxVersion = plan.attribute( "version", PLANWORK_FILE_SYNTAX_VERSION ); m_xmlLoader.setWorkVersion( syntaxVersion ); if ( syntaxVersion > PLANWORK_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document is a newer version than supported by PlanWork (syntax version: %1)
" "Opening it in this version of PlanWork will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { return false; } } m_xmlLoader.setVersion( plan.attribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ) ); m_xmlLoader.startLoad(); WorkPackage *wp = new WorkPackage( m_loadingFromProjectStore ); wp->loadXML( plan, m_xmlLoader ); m_xmlLoader.stopLoad(); if ( ! setWorkPackage( wp, store ) ) { // rejected, so nothing changed... return true; } emit changed(); return true; } bool Part::loadKPlatoXML( const KoXmlDocument &document, KoStore* ) { debugPlanWork; QString value; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlanWork << "No mime type specified!" << endl; KMessageBox::error( 0, i18n( "Invalid document. No mimetype specified." ) ); return false; } else if ( value != "application/x-vnd.kde.kplato.work" ) { errorPlanWork << "Unknown mime type " << value; KMessageBox::error( 0, i18n( "Invalid document. Expected mimetype application/x-vnd.kde.kplato.work, got %1", value ) ); return false; } QString syntaxVersion = plan.attribute( "version", KPLATOWORK_MAX_FILE_SYNTAX_VERSION ); m_xmlLoader.setWorkVersion( syntaxVersion ); if ( syntaxVersion > KPLATOWORK_MAX_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document is a newer version than supported by PlanWork (syntax version: %1)
" "Opening it in this version of PlanWork will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { return false; } } m_xmlLoader.setMimetype( value ); m_xmlLoader.setVersion( plan.attribute( "kplato-version", KPLATO_MAX_FILE_SYNTAX_VERSION ) ); m_xmlLoader.startLoad(); WorkPackage *wp = new WorkPackage( m_loadingFromProjectStore ); wp->loadKPlatoXML( plan, m_xmlLoader ); m_xmlLoader.stopLoad(); if ( ! setWorkPackage( wp ) ) { // rejected, so nothing changed... return true; } emit changed(); return true; } bool Part::completeLoading( KoStore * ) { return true; } QUrl Part::extractFile( const Document *doc ) { WorkPackage *wp = findWorkPackage( doc ); return wp == 0 ? QUrl() : wp->extractFile( doc ); } int Part::docType( const Document *doc ) const { DocumentChild *ch = findChild( doc ); if ( ch == 0 ) { return DocumentChild::Type_Unknown; } return ch->type(); } DocumentChild *Part::findChild( const Document *doc ) const { foreach ( const WorkPackage *wp, m_packageMap ) { DocumentChild *c = wp->findChild( doc ); if ( c ) { return c; } } return 0; } WorkPackage *Part::findWorkPackage( const Document *doc ) const { foreach ( const WorkPackage *wp, m_packageMap ) { if ( wp->contains( doc ) ) { return const_cast( wp ); } } return 0; } WorkPackage *Part::findWorkPackage( const DocumentChild *child ) const { foreach ( const WorkPackage *wp, m_packageMap ) { if ( wp->contains( child ) ) { return const_cast( wp ); } } return 0; } WorkPackage *Part::findWorkPackage( const Node *node ) const { return m_packageMap.value( node->projectNode()->id() + node->id() ); } bool Part::editWorkpackageDocument( const Document *doc ) { //debugPlanWork<url(); // start in any suitable application return editOtherDocument( doc ); } bool Part::editOtherDocument( const Document *doc ) { Q_ASSERT( doc != 0 ); //debugPlanWork<url(); WorkPackage *wp = findWorkPackage( doc ); if ( wp == 0 ) { KMessageBox::error( 0, i18n( "Edit failed. Cannot find a work package." ) ); return false; } return wp->addChild( this, doc ); } void Part::viewWorkpackageDocument( Document *doc ) { debugPlanWork<sendAs() == Document::SendAs_Copy ) { filename = extractFile( doc ); } else { filename = doc->url(); } // open for view viewDocument( filename ); } bool Part::removeDocument( Document *doc ) { if ( doc == 0 ) { return false; } WorkPackage *wp = findWorkPackage( doc ); if ( wp == 0 ) { return false; } return wp->removeDocument( this, doc ); } bool Part::viewDocument( const QUrl &filename ) { debugPlanWork<<"url:"<isModified() ) { saveWorkPackage( wp ); } } m_undostack->setClean(); } void Part::saveWorkPackage( WorkPackage *wp ) { wp->saveToProjects( this ); } bool Part::saveWorkPackages( bool silent ) { debugPlanWork<saveToProjects( this ); } m_undostack->setClean(); return true; } bool Part::completeSaving( KoStore */*store*/ ) { return true; } QDomDocument Part::saveXML() { debugPlanWork; return QDomDocument(); } bool Part::queryClose() { debugPlanWork; QList modifiedList; foreach ( WorkPackage *wp, m_packageMap ) { switch ( wp->queryClose( this ) ) { case KMessageBox::No: modifiedList << wp; break; case KMessageBox::Cancel: debugPlanWork<<"Cancel"; return false; } } // closeEvent calls queryClose so modified must be reset or else wps are queried all over again foreach ( WorkPackage *wp, modifiedList ) { wp->setModified( false ); } setModified( false ); return true; } bool Part::openFile() { debugPlanWork< 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 "taskworkpackageview.h" #include "taskworkpackagemodel.h" #include "workpackage.h" #include "part.h" #include "kptglobal.h" #include "kptcommand.h" #include "kptproject.h" #include "kptschedule.h" #include "kpteffortcostmap.h" #include "kptitemviewsettup.h" #include "calligraplanworksettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debugarea.h" using namespace KPlato; namespace KPlatoWork { TaskWorkPackageTreeView::TaskWorkPackageTreeView( Part *part, QWidget *parent ) : DoubleTreeViewBase( parent ) { setContextMenuPolicy( Qt::CustomContextMenu ); masterView()->header()->setSortIndicatorShown( true ); masterView()->header()->setSectionsClickable( true ); slaveView()->header()->setSortIndicatorShown( true ); slaveView()->header()->setSectionsClickable( true ); QSortFilterProxyModel *sf = new QSortFilterProxyModel( this ); TaskWorkPackageModel *m = new TaskWorkPackageModel( part, sf ); sf->setSourceModel( m ); setModel( sf ); //setSelectionBehavior( QAbstractItemView::SelectItems ); setSelectionMode( QAbstractItemView::SingleSelection ); setStretchLastSection( false ); createItemDelegates( m ); QList lst1; lst1 << 2 << -1; // display column 0 and 1 (NodeName and NodeType ) in left view masterView()->setDefaultColumns( QList() << 0 << 1 ); QList show; show << TaskWorkPackageModel::NodeCompleted << TaskWorkPackageModel::NodeActualEffort << TaskWorkPackageModel::NodeRemainingEffort << TaskWorkPackageModel::NodePlannedEffort << TaskWorkPackageModel::NodeStartTime << TaskWorkPackageModel::NodeActualStart << TaskWorkPackageModel::NodeEndTime << TaskWorkPackageModel::NodeActualFinish << TaskWorkPackageModel::ProjectName << TaskWorkPackageModel::ProjectManager; QList lst2; for ( int i = 0; i < m->columnCount(); ++i ) { if ( ! show.contains( i ) ) { lst2 << i; } } hideColumns( lst1, lst2 ); slaveView()->setDefaultColumns( show ); masterView()->setFocus(); debugPlanWork<taskWorkPackageView(); masterView()->header()->setSectionsClickable( true ); slaveView()->header()->setSortIndicatorShown( true ); connect(masterView()->header(), &QHeaderView::sortIndicatorChanged, this, &TaskWorkPackageTreeView::setSortOrder); connect(slaveView()->header(), &QHeaderView::sortIndicatorChanged, this, &TaskWorkPackageTreeView::setSortOrder); masterView()->header()->setSortIndicator( TaskWorkPackageModel::NodeType, Qt::AscendingOrder ); connect(masterView()->header(), &QHeaderView::sectionMoved, this, &TaskWorkPackageTreeView::sectionsMoved); connect(slaveView()->header(), &QHeaderView::sectionMoved, this, &TaskWorkPackageTreeView::sectionsMoved); } void TaskWorkPackageTreeView::setSortOrder( int col, Qt::SortOrder order ) { model()->sort( col, order ); } TaskWorkPackageModel *TaskWorkPackageTreeView::itemModel() const { return static_cast( static_cast( model() )->sourceModel() ); } Project *TaskWorkPackageTreeView::project() const { return itemModel()->project(); } Document *TaskWorkPackageTreeView::currentDocument() const { QSortFilterProxyModel *sf = qobject_cast( model() ); Q_ASSERT( sf ); if ( sf == 0 ) { return 0; } return itemModel()->documentForIndex( sf->mapToSource( selectionModel()->currentIndex() ) ); } Node *TaskWorkPackageTreeView::currentNode() const { QSortFilterProxyModel *sf = qobject_cast( model() ); Q_ASSERT( sf ); if ( sf == 0 ) { return 0; } return itemModel()->nodeForIndex( sf->mapToSource( selectionModel()->currentIndex() ) ); } QList TaskWorkPackageTreeView::selectedNodes() const { QList lst; QSortFilterProxyModel *sf = qobject_cast( model() ); Q_ASSERT( sf ); if ( sf == 0 ) { return lst; } foreach( const QModelIndex &idx, selectionModel()->selectedIndexes() ) { QModelIndex i = sf->mapToSource( idx ); Q_ASSERT( i.isValid() && i.model() == itemModel() ); Node *n = itemModel()->nodeForIndex( i ); if ( n && ! lst.contains( n ) ) { lst << n; } } return lst; } void TaskWorkPackageTreeView::setProject( Project *project ) { itemModel()->setProject( project ); } void TaskWorkPackageTreeView::slotActivated( const QModelIndex &index ) { debugPlanWork<source() != this || !(event->possibleActions() & Qt::MoveAction))) return; TreeViewBase::dragMoveEvent( event ); if ( ! event->isAccepted() ) { return; } //QTreeView thinks it's ok to drop event->ignore(); QModelIndex index = indexAt( event->pos() ); if ( ! index.isValid() ) { event->accept(); return; // always ok to drop on main project } Node *dn = model()->node( index ); if ( dn == 0 ) { errorPlanWork<<"no node to drop on!" return; // hmmm } switch ( dropIndicatorPosition() ) { case AboveItem: case BelowItem: //dn == sibling if ( model()->dropAllowed( dn->parentNode(), event->mimeData() ) ) { event->accept(); } break; case OnItem: //dn == new parent if ( model()->dropAllowed( dn, event->mimeData() ) ) { event->accept(); } break; default: break; }*/ } //----------------------------------- AbstractView::AbstractView( Part *part, QWidget *parent ) : QWidget( parent ), m_part( part ) { } void AbstractView::updateReadWrite( bool /*rw*/ ) { } QList AbstractView::selectedNodes() const { return QList(); } Node *AbstractView::currentNode() const { return 0; } Document *AbstractView::currentDocument() const { return 0; } void AbstractView::slotHeaderContextMenuRequested( const QPoint &pos ) { debugPlanWork; QList lst = contextActionList(); if ( ! lst.isEmpty() ) { QMenu::exec( lst, pos, lst.first() ); } } void AbstractView::slotContextMenuRequested( const QModelIndex &/*index*/, const QPoint& pos ) { return slotHeaderContextMenuRequested( pos ); } void AbstractView::slotContextMenuRequested( Node *node, const QPoint& pos ) { debugPlanWork<name()<<" :"<type() ) { case Node::Type_Task: name = "taskstatus_popup"; break; case Node::Type_Milestone: name = "taskview_milestone_popup"; break; case Node::Type_Summarytask: name = "taskview_summary_popup"; break; default: break; } debugPlanWork<url()<<" :"<type() ) { case Document::Type_Product: name = "editdocument_popup"; break; default: name = "viewdocument_popup"; break; } debugPlanWork<setMargin( 0 ); m_view = new TaskWorkPackageTreeView( part, this ); l->addWidget( m_view ); setupGui(); connect( itemModel(), &KPlato::ItemModelBase::executeCommand, part, &Part::addCommand ); connect( m_view, SIGNAL(contextMenuRequested(QModelIndex,QPoint,QModelIndexList)), SLOT(slotContextMenuRequested(QModelIndex,QPoint)) ); connect( m_view, &KPlato::DoubleTreeViewBase::headerContextMenuRequested, this, &TaskWorkPackageView::slotHeaderContextMenuRequested ); connect( m_view, &KPlato::DoubleTreeViewBase::selectionChanged, this, &TaskWorkPackageView::slotSelectionChanged ); loadContext(); connect(m_view, &TaskWorkPackageTreeView::sectionsMoved, this, &TaskWorkPackageView::sectionsMoved); } void TaskWorkPackageView::updateReadWrite( bool rw ) { m_view->setReadWrite( rw ); } void TaskWorkPackageView::slotSelectionChanged( const QModelIndexList &/*lst*/ ) { emit selectionChanged(); } QList TaskWorkPackageView::selectedNodes() const { return m_view->selectedNodes(); } Node *TaskWorkPackageView::currentNode() const { return m_view->currentNode(); } Document *TaskWorkPackageView::currentDocument() const { return m_view->currentDocument(); } void TaskWorkPackageView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos ) { debugPlanWork<( m_view->model() ); Q_ASSERT( sf ); if ( sf == 0 ) { return; } QModelIndex idx = sf->mapToSource( index ); if ( ! idx.isValid() ) { slotHeaderContextMenuRequested( pos ); return; } Node *node = itemModel()->nodeForIndex( idx ); if ( node ) { return slotContextMenuRequested( node, pos ); } Document *doc = itemModel()->documentForIndex( idx ); if ( doc ) { return slotContextMenuRequested( doc, pos ); } return slotHeaderContextMenuRequested( pos ); } void TaskWorkPackageView::setupGui() { // Add the context menu actions for the view options connect(m_view->actionSplitView(), &QAction::triggered, this, &TaskWorkPackageView::slotSplitView); addContextAction( m_view->actionSplitView() ); actionOptions = new QAction(koIcon("configure"), i18n("Configure View..."), this); connect(actionOptions, &QAction::triggered, this, &TaskWorkPackageView::slotOptions); addContextAction( actionOptions ); } void TaskWorkPackageView::slotSplitView() { debugPlanWork; m_view->setViewSplitMode( ! m_view->isViewSplit() ); saveContext(); } void TaskWorkPackageView::slotOptions() { debugPlanWork; QPointer dlg = new SplitItemViewSettupDialog( 0, m_view, this ); dlg->exec(); delete dlg; saveContext(); } bool TaskWorkPackageView::loadContext() { KoXmlDocument doc; doc.setContent( PlanWorkSettings::self()->taskWorkPackageView() ); KoXmlElement context = doc.namedItem( "TaskWorkPackageViewSettings" ).toElement(); if ( context.isNull() ) { debugPlanWork<<"No settings"; return false; } return m_view->loadContext( itemModel()->columnMap(), context ); } void TaskWorkPackageView::saveContext() { QDomDocument doc ( "TaskWorkPackageView" ); QDomElement context = doc.createElement( "TaskWorkPackageViewSettings" ); doc.appendChild( context ); m_view->saveContext( itemModel()->columnMap(), context ); PlanWorkSettings::self()->setTaskWorkPackageView( doc.toString() ); PlanWorkSettings::self()->save(); debugPlanWork<( idx.data( KGantt::ItemTypeRole ).toInt() ); QString txt = itemText( idx, typ ); QRectF itemRect = opt.itemRect; // painter->save(); // painter->setPen( Qt::blue ); // painter->drawRect( opt.boundingRect.adjusted( -1., -1., 1., 1. ) ); // painter->setPen( Qt::red ); // painter->drawRect( itemRect ); // painter->restore(); QRectF textRect = itemRect; if ( ! txt.isEmpty() ) { int tw = opt.fontMetrics.width( txt ) + static_cast( itemRect.height()/1.5 ); switch( opt.displayPosition ) { case KGantt::StyleOptionGanttItem::Left: textRect.adjust( -tw, 0.0, 0.0, 0.0 ); break; case KGantt::StyleOptionGanttItem::Right: textRect.adjust( 0.0, 0.0, tw, 0.0 ); break; default: break; } } painter->save(); QPen pen = defaultPen( typ ); if ( opt.state & QStyle::State_Selected ) pen.setWidth( 2*pen.width() ); painter->setPen( pen ); qreal pw = painter->pen().width()/2.; switch( typ ) { case KGantt::TypeTask: if ( itemRect.isValid() ) { pw-=1; QRectF r = itemRect; r.translate( 0., r.height()/6. ); r.setHeight( 2.*r.height()/3. ); painter->save(); painter->setBrushOrigin( itemRect.topLeft() ); painter->translate( 0.5, 0.5 ); bool normal = true; if ( showStatus ) { int state = data( idx, TaskWorkPackageModel::NodeStatus, Qt::EditRole ).toInt(); if ( state & Node::State_NotScheduled ) { painter->setBrush( m_brushes[ Brush_NotScheduled ] ); normal = false; } else if ( state & Node::State_Finished ) { painter->setBrush( m_brushes[ Brush_Finished ] ); normal = false; } else if ( state & Node::State_Started ) { if ( state & Node::State_Late ) { painter->setBrush( m_brushes[ Brush_Late ] ); normal = false; } } else { // scheduled, not started, not finished if ( state & Node::State_Late ) { painter->setBrush( m_brushes[ Brush_Late ] ); normal = false; } else if ( state & Node::State_NotReadyToStart ) { painter->setBrush( m_brushes[ Brush_NotReadyToStart ] ); normal = false; } else if ( state & Node::State_ReadyToStart ) { painter->setBrush( m_brushes[ Brush_ReadyToStart ] ); normal = false; } } } else if ( showCriticalTasks ) { bool critical = data( idx, NodeModel::NodeCritical, Qt::DisplayRole ).toBool(); if ( ! critical && showCriticalPath ) { critical = data( idx, NodeModel::NodeCriticalPath, Qt::DisplayRole ).toBool(); } if ( critical ) { QVariant br = data( idx, NodeModel::NodeCritical, Role::Foreground ); painter->setBrush( br.isValid() ? br.value() : m_criticalBrush ); normal = false; } } if ( normal ) { painter->setBrush( m_brushes[ Brush_Normal ] ); } painter->drawRect( r ); if ( showProgress ) { bool ok; qreal completion = idx.model()->data( idx, KGantt::TaskCompletionRole ).toDouble( &ok ); if ( ok ) { qreal h = r.height(); QRectF cr( r.x(), r.y()+h/4. + 1, r.width()*completion/100., h/2. - 2 ); painter->fillRect( cr, painter->pen().brush() ); } } painter->restore(); // only Left/Center/Right used const Qt::Alignment ta = (opt.displayPosition == KGantt::StyleOptionGanttItem::Left) ? Qt::AlignLeft : (opt.displayPosition == KGantt::StyleOptionGanttItem::Right) ? Qt::AlignRight : /* KGantt::StyleOptionGanttItem::Center*/ Qt::AlignCenter; painter->drawText( textRect, ta, txt ); } break; default: break; } painter->restore(); } QString GanttItemDelegate::toolTip( const QModelIndex &idx ) const { if ( !idx.isValid() || ! idx.model() ) { return QString(); } const QAbstractItemModel* model = idx.model(); if ( data( idx, TaskWorkPackageModel::NodeFinished, Qt::EditRole ).toBool() ) { // finished return xi18nc( "@info:tooltip", "Task: %1" "Actual finish: %2" "Planned finish: %3" "Status: %4" "Project: %5", model->data( idx, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeActualFinish, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeEndTime, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeStatus, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::ProjectName, Qt::DisplayRole ).toString() ); } if ( data( idx, TaskWorkPackageModel::NodeStarted, Qt::EditRole ).toBool() ) { // started return xi18nc( "@info:tooltip", "Task: %1" "Completion: %2 %" "Actual start: %3" "Planned: %4 - %5" "Status: %6" "Project: %7", model->data( idx, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeCompleted, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeActualStart, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeStartTime, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeEndTime, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeStatus, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::ProjectName, Qt::DisplayRole ).toString() ); } // Planned KGantt::StyleOptionGanttItem opt; int typ = data( idx, NodeModel::NodeType, Qt::EditRole ).toInt(); switch ( typ ) { case Node::Type_Task: return xi18nc( "@info:tooltip", "Task: %1" "Planned: %2 - %3" "Status: %4" "Project: %5", model->data( idx, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeStartTime, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeEndTime, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::NodeStatus, Qt::DisplayRole ).toString(), data( idx, TaskWorkPackageModel::ProjectName, Qt::DisplayRole ).toString() ); } return QString(); } GanttView::GanttView( Part *part, QWidget *parent ) : KPlato::GanttViewBase( parent ), m_part( part ), m_project( 0 ), m_ganttdelegate( new GanttItemDelegate( this ) ), m_itemmodel( new TaskWorkPackageModel( part, this ) ) { debugPlanWork<<"------------------- create GanttView -----------------------"; m_itemmodel->setObjectName( "Gantt model" ); graphicsView()->setItemDelegate( m_ganttdelegate ); GanttTreeView *tv = new GanttTreeView( this ); tv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); tv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); tv->setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); // needed since qt 4.2 setLeftView( tv ); m_rowController = new KGantt::TreeViewRowController( tv, ganttProxyModel() ); setRowController( m_rowController ); tv->header()->setStretchLastSection( true ); KGantt::View::setModel( m_itemmodel ); QList show; show << TaskWorkPackageModel::NodeName << TaskWorkPackageModel::NodeDescription; tv->setDefaultColumns( show ); for ( int i = 0; i < m_itemmodel->columnCount(); ++i ) { if ( ! show.contains( i ) ) { tv->hideColumn( i ); } } debugPlanWork<<"mapping roles"; KGantt::ProxyModel *m = static_cast( ganttProxyModel() ); m->setRole( KGantt::ItemTypeRole, KGantt::ItemTypeRole ); // To provide correct format m->setRole( KGantt::StartTimeRole, Qt::EditRole ); // To provide correct format m->setRole( KGantt::EndTimeRole, Qt::EditRole ); // To provide correct format m->setColumn( KGantt::ItemTypeRole, TaskWorkPackageModel::NodeType ); m->setColumn( KGantt::StartTimeRole, TaskWorkPackageModel::NodeStartTime ); m->setColumn( KGantt::EndTimeRole, TaskWorkPackageModel::NodeEndTime ); m->setColumn( KGantt::TaskCompletionRole, TaskWorkPackageModel::NodeCompleted ); debugPlanWork<<"roles mapped"; KGantt::DateTimeGrid *g = static_cast( grid() ); g->setDayWidth( 30 ); // TODO: extend QLocale/KGantt to support formats for hourly time display // see bug #349030 // removed custom code here for ( int i = 0; i < part->workPackageCount(); ++i ) { updateDateTimeGrid( part->workPackage( i ) ); } connect( m_itemmodel, &QAbstractItemModel::rowsInserted, this, &GanttView::slotRowsInserted); connect( m_itemmodel, &QAbstractItemModel::rowsRemoved, this, &GanttView::slotRowsRemoved); connect(tv, &KPlato::TreeViewBase::contextMenuRequested, this, &GanttView::contextMenuRequested); connect(tv, &KPlato::TreeViewBase::headerContextMenuRequested, this, &GanttView::headerContextMenuRequested); connect(tv->selectionModel(), &QItemSelectionModel::selectionChanged, this, &GanttView::slotSelectionChanged); connect(tv->header(), &QHeaderView::sectionMoved, this, &GanttView::sectionsMoved); } GanttView::~GanttView() { delete m_rowController; } void GanttView::slotSelectionChanged( const QItemSelection &selected, const QItemSelection& ) { emit selectionChanged( selected.indexes() ); } void GanttView::slotRowsInserted( const QModelIndex &parent, int start, int end ) { debugPlanWork<workPackage( i ) ); } } } void GanttView::slotRowsRemoved( const QModelIndex &/*parent*/, int /*start*/, int /*end*/ ) { KGantt::DateTimeGrid *g = static_cast( grid() ); - g->setStartDateTime( QDateTime() ); + QDateTime newStart; for ( int i = 0; i < m_part->workPackageCount(); ++i ) { - updateDateTimeGrid( m_part->workPackage( i ) ); + WorkPackage *wp = m_part->workPackage(i); + Task *task = static_cast(wp->project()->childNode(0)); + if (!newStart.isValid() || newStart > task->startTime()) { + newStart = task->startTime(); + } + } + if (newStart.isValid()) { + g->setStartDateTime(newStart); } } void GanttView::updateDateTimeGrid( WorkPackage *wp ) { debugPlanWork<project() || ! wp->project()->childNode( 0 ) ) { return; } Task *task = static_cast( wp->project()->childNode( 0 ) ); DateTime st = task->startTime(); if ( ! st.isValid() && task->completion().startTime().isValid() ) { st = qMin( st, task->completion().startTime() ); } if ( ! st.isValid() ) { return; } KGantt::DateTimeGrid *g = static_cast( grid() ); QDateTime gst = g->startDateTime(); if ( ! gst.isValid() || gst > st ) { st.setTime(QTime(0, 0, 0, 0)); g->setStartDateTime( st ); } } TaskWorkPackageModel *GanttView::itemModel() const { return m_itemmodel; } void GanttView::setProject( Project *project ) { itemModel()->setProject( project ); m_project = project; } QList GanttView::selectedNodes() const { QList nodes; foreach( const QModelIndex &idx, treeView()->selectionModel()->selectedRows() ) { nodes << itemModel()->nodeForIndex( idx ); } return nodes; } Node *GanttView::currentNode() const { return itemModel()->nodeForIndex( treeView()->selectionModel()->currentIndex() ); } bool GanttView::loadContext( const KoXmlElement &context ) { KoXmlElement e = context.namedItem( "itemview" ).toElement(); if ( ! e.isNull() ) { treeView()->loadContext( itemModel()->columnMap(), e ); } e = context.namedItem( "ganttview" ).toElement(); if ( ! e.isNull() ) { KPlato::GanttViewBase::loadContext( e ); } return true; } void GanttView::saveContext( QDomElement &context ) const { QDomElement e = context.ownerDocument().createElement( "itemview" ); context.appendChild( e ); treeView()->saveContext( itemModel()->columnMap(), e ); e = context.ownerDocument().createElement( "ganttview" ); context.appendChild( e ); KPlato::GanttViewBase::saveContext( e ); } //----------------------------------- TaskWPGanttView::TaskWPGanttView( Part *part, QWidget *parent ) : AbstractView( part, parent ) { debugPlanWork<<"-------------------- creating TaskWPGanttView -------------------"; QVBoxLayout * l = new QVBoxLayout( this ); l->setMargin( 0 ); m_view = new GanttView( part, this ); l->addWidget( m_view ); setupGui(); connect(itemModel(), &KPlato::ItemModelBase::executeCommand, part, &Part::addCommand); connect( m_view, SIGNAL(contextMenuRequested(QModelIndex,QPoint)), SLOT(slotContextMenuRequested(QModelIndex,QPoint))); connect( m_view, &GanttView::headerContextMenuRequested, this, &TaskWPGanttView::slotHeaderContextMenuRequested); connect( m_view, &GanttView::selectionChanged, this, &TaskWPGanttView::slotSelectionChanged); connect(m_view, &GanttView::sectionsMoved, this, &TaskWPGanttView::sectionsMoved); } void TaskWPGanttView::slotSelectionChanged( const QModelIndexList& /*lst*/ ) { emit selectionChanged(); } QList TaskWPGanttView::selectedNodes() const { return m_view->selectedNodes(); } Node *TaskWPGanttView::currentNode() const { return m_view->currentNode(); } void TaskWPGanttView::slotContextMenuRequested( const QModelIndex &idx, const QPoint& pos ) { debugPlanWork<nodeForIndex( idx ); if ( node ) { return slotContextMenuRequested( node, pos ); } Document *doc = itemModel()->documentForIndex( idx ); if ( doc ) { return slotContextMenuRequested( doc, pos ); } return slotHeaderContextMenuRequested( pos ); } void TaskWPGanttView::setupGui() { actionOptions = new QAction(koIcon("configure"), i18n("Configure View..."), this); connect(actionOptions, &QAction::triggered, this, &TaskWPGanttView::slotOptions); addContextAction( actionOptions ); } void TaskWPGanttView::slotOptions() { debugPlanWork; QPointer dlg = new ItemViewSettupDialog( 0, m_view->treeView(), true, this ); dlg->exec(); delete dlg; saveContext(); } bool TaskWPGanttView::loadContext() { KoXmlDocument doc; doc.setContent( PlanWorkSettings::self()->taskWPGanttView() ); KoXmlElement context = doc.namedItem( "TaskWPGanttViewSettings" ).toElement(); if ( context.isNull() ) { debugPlanWork<<"No settings"; return false; } return m_view->loadContext( context ); } void TaskWPGanttView::saveContext() { QDomDocument doc ( "TaskWPGanttView" ); QDomElement context = doc.createElement( "TaskWPGanttViewSettings" ); doc.appendChild( context ); m_view->saveContext( context ); PlanWorkSettings::self()->setTaskWPGanttView( doc.toString() ); PlanWorkSettings::self()->save(); debugPlanWork<